PLearn 0.1
PPath.cc
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 
00003 // PPath.cc
00004 //
00005 // Copyright (C) 2005 Pascal Vincent 
00006 // 
00007 // Redistribution and use in source and binary forms, with or without
00008 // modification, are permitted provided that the following conditions are met:
00009 // 
00010 //  1. Redistributions of source code must retain the above copyright
00011 //     notice, this list of conditions and the following disclaimer.
00012 // 
00013 //  2. Redistributions in binary form must reproduce the above copyright
00014 //     notice, this list of conditions and the following disclaimer in the
00015 //     documentation and/or other materials provided with the distribution.
00016 // 
00017 //  3. The name of the authors may not be used to endorse or promote
00018 //     products derived from this software without specific prior written
00019 //     permission.
00020 // 
00021 // THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
00022 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00023 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
00024 // NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00025 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
00026 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
00027 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
00028 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
00029 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00030 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00031 // 
00032 // This file is part of the PLearn library. For more information on the PLearn
00033 // library, go to the PLearn Web site at www.plearn.org
00034 
00035 /* *******************************************************      
00036  * $Id: PPath.cc 9774 2008-12-11 21:06:26Z nouiz $ 
00037  ******************************************************* */
00038 
00039 // Authors: Christian Dorion
00040 
00043 // #define PL_LOG_MODULE_NAME "PPath"
00044 
00045 #include <ctype.h>
00046 #include <nspr/prenv.h>
00047 
00048 #include "PPath.h"
00049 #include "PStream.h"
00050 #include "openFile.h"
00051 #include "pl_log.h"
00052 #include "fileutils.h"
00053 #include <plearn/base/stringutils.h>    
00054 
00056 // DOS SETTINGS
00057 #if defined(WIN32) && !defined(__CYGWIN__)
00058 
00059 #include <direct.h>
00060 #define SYS_GETCWD     _getcwd
00061   
00063 // POSIX SETTINGS
00064 #else
00065 
00066 #include <unistd.h>
00067 #define SYS_GETCWD     ::getcwd
00068 
00069 #endif
00070 
00071 #define PPATH_SLASH '/'  // The canonical slash.
00072 
00073 namespace PLearn {
00074 using namespace std;
00075 
00077 //  Stringutils.h         //////////////////////////////
00078 bool startsWith(const string& str, const char& c) 
00079 {
00080     if (str.empty())
00081         return false;
00082     return str[0] == c;
00083 }
00084 
00085 bool endsWith  (const string& str, const char& c) 
00086 {
00087     if (str.empty())
00088         return false;
00089     return str[str.length()-1] == c;
00090 }
00091 
00092 bool startsWith(const string& str, const string& s) 
00093 {
00094     if ( s.length() > str.length() )
00095         return false;
00096     return str.substr(0, s.length()) == s;
00097 }
00098 
00099 bool endsWith  (const string& str, const string& s) 
00100 {
00101     if ( s.length() > str.length() )
00102         return false;
00103     return str.substr(str.length()-s.length()) == s;
00104 }
00106 
00107 
00109 // DOS SETTINGS
00110 #if defined(WIN32)
00111 string PPath::forbidden_chars()
00112 {
00113     return string();
00114 }
00115 
00116 const string& PPath::_slash()
00117 {
00118     static string s = "\\"; 
00119     return s;
00120 }
00121 
00122 char          PPath::_slash_char()    { return '\\'; }  
00123   
00125 // POSIX SETTINGS
00126 #else
00127 // Even if the posix standard allows backslashes in file paths,
00128 // PLearn users should never use those since PLearn aims at full and
00129 // easy portability.
00130 //
00131 // Other chars may be forbidden soon.
00132 string PPath::forbidden_chars()
00133 {
00134     return "\\";
00135 }
00136 
00137 const string& PPath::_slash()
00138 {
00139     static string s = "/"; 
00140     return s; 
00141 }
00142 
00143 char   PPath::_slash_char()    { return '/';  }
00144 
00145 #endif  
00146 
00147   
00149 // In the scope of the PLearn namespace 
00150 PStream& operator<<(PStream& out, const PPath& path)
00151 {
00152     switch (out.outmode) {
00153     case PStream::raw_ascii:
00154     case PStream::pretty_ascii:
00155     {
00156         out << path.c_str();
00157         break;
00158     }
00159     case PStream::plearn_ascii:
00160     case PStream::plearn_binary:
00161     {
00162         out << path.canonical();
00163         break;
00164     }
00165     default:
00166         PLERROR("This PStream mode is not supported for PPath");
00167     }
00168     return out;
00169 }
00170 
00171 PStream& operator>>(PStream& in, PPath& path)
00172 {
00173     string spath;
00174     in >> spath;
00175     switch (in.inmode) {
00176     case PStream::raw_ascii:
00177     case PStream::pretty_ascii:
00178     case PStream::plearn_ascii:
00179     case PStream::plearn_binary:
00180     {
00181         path = PPath(spath);
00182         break;
00183     }
00184     default:
00185         PLERROR("In operator>> - This PStream mode is not supported for PPath");
00186     }
00187     return in;
00188 }
00189 
00191 // Static PPath methods
00192 
00194 // home //
00196 PPath PPath::home()
00197 {
00198     // Supply a default value so PLearn does not crash.
00199     // when $HOME isn't defined.
00200 #ifdef WIN32
00201 #define PL_DEFAULT_HOME PPath("C:\\")
00202 #else
00203 #define PL_DEFAULT_HOME PPath("/")
00204 #endif
00205     return PPath::getenv("HOME", PL_DEFAULT_HOME);
00206 }
00207 
00209 // getcwd //
00211 PPath PPath::getcwd()
00212 {
00213     // TODO Use a NSPR function when there is one:
00214     //      https://bugzilla.mozilla.org/show_bug.cgi?id=280953
00215     char buf[2000];
00216     if (!SYS_GETCWD(buf, 2000))
00217         // Error while reading the current directory. One should probably use a
00218         // larger buffer, but it is even easier to crash.
00219         PLERROR("In PPath::getcwd - Could not obtain the current working "
00220                 "directory, a larger buffer may be necessary");
00221     return PPath(buf);
00222 }
00223 
00225 // getenv //
00227 PPath PPath::getenv(const string& var, const PPath& default_)
00228 {
00229     char* env_var = PR_GetEnv(var.c_str());
00230     if ( env_var )
00231         return PPath( env_var );
00232     return default_;
00233 }
00234 
00236 // metaprotocolToMetapath //
00238 
00239 // Static map that stores the binding metaprotocol <-> metapath.
00240 // It is embedded within a function to prevent potential compiler issues during
00241 // static initialization (e.g. under Windows with gcc 3.4.4).
00242 map<string, PPath>& metaprotocol_to_metapath() {
00243     static map<string, PPath> metaprotocol_to_metapath;
00244     return metaprotocol_to_metapath;
00245 }
00246 
00247 const map<string, PPath>& PPath::metaprotocolToMetapath()
00248 {
00249     static  bool                mappings;
00250   
00251     if ( !mappings )
00252     {
00253         // Avoiding infinite loop.
00254         mappings = true;
00255 
00256         PPath   plearn_configs   = PPath::getenv( "PLEARN_CONFIGS",
00257                                                   PPath::home() / ".plearn" );    
00258 
00259         PPath   config_file_path = plearn_configs / "ppath.config";
00260 
00261         if (isfile(config_file_path))
00262         {
00263             PStream ppath_config = openFile(config_file_path,
00264                                             PStream::plearn_ascii);
00265 
00266             string  next_metaprotocol;
00267             PPath   next_metapath;    
00268             while (ppath_config.good()) {
00269                 ppath_config >> next_metaprotocol >> next_metapath;
00270                 if (next_metaprotocol.empty()){
00271                     if (ppath_config.good())
00272                         PLERROR("In PPath::metaprotocolToMetapath - Error while parsing PPath config file (%s): read "
00273                                 "a blank line before reaching the end of the file",
00274                                 config_file_path.errorDisplay().c_str());
00275                     else
00276                         // Nothing left to read.
00277                         break;
00278                 }
00279                 // Make sure we managed to read the metapath associated with the metaprotocol.
00280                 if (next_metapath.empty())
00281                     PLERROR("In PPath::metaprotocolToMetapath - Error in PPath config file (%s): could not read the "
00282                             "path associated with '%s'",
00283                             config_file_path.errorDisplay().c_str(), next_metaprotocol.c_str());
00284         
00285                 // For the sake of simplicity, we do not allow a metapath to end with
00286                 // a slash unless it is a root directory.
00287                 next_metapath.removeTrailingSlash();
00288         
00289                 if (!addMetaprotocolBinding(next_metaprotocol, next_metapath))
00290                     PLWARNING("In PPath::metaprotocolToMetapath - Metaprotocol"
00291                               " '%s' is being redefined, please check your "
00292                               "ppath.config (%s)",
00293                               next_metaprotocol.c_str(),
00294                               config_file_path.errorDisplay().c_str());
00295             }       
00296         }
00297         else
00298         {
00299             if (PR_GetEnv("HOME"))
00300             {
00301                 // Default PPath settings. Defined only if the HOME environment
00302                 // variable exists.
00303                 metaprotocol_to_metapath()["HOME"] = "${HOME}";
00304                 metaprotocol_to_metapath()["PLEARNDIR"] = "HOME:PLearn";
00305                 metaprotocol_to_metapath()["PLEARN_LIBDIR"] = "PLEARNDIR:external_libs";
00306             }
00307         }
00308     }
00309 
00310     return metaprotocol_to_metapath();
00311 }
00312 
00314 // addMetaprotocolBinding //
00316 bool PPath::addMetaprotocolBinding(const string& metaprotocol,
00317                                    const PPath& metapath,
00318                                    bool  force)
00319 {
00320     const map<string, PPath>& bindings = metaprotocolToMetapath();
00321     bool already_here = bindings.find(metaprotocol) != bindings.end();
00322     if (!already_here || force)
00323         metaprotocol_to_metapath()[metaprotocol] = metapath;
00324     return !already_here;
00325 }
00326 
00327 
00328 // This method MUST NOT return a path since it would lead to an infinite
00329 // loop of constructors.
00330 string PPath::expandEnvVariables(const string& path)
00331 {
00332     string expanded     = path;
00333     size_t begvar       = expanded.find( "${" );
00334     size_t endvar       = expanded.find(  "}" );
00335   
00336     while ( begvar != npos && endvar != npos  )
00337     {
00338         size_t start       = begvar+2;
00339         size_t len         = endvar - start;
00340         string envvar      = expanded.substr(start, len);
00341         PPath  envpath     = PPath::getenv(envvar);
00342 
00343         if ( envpath == "" )
00344             PLERROR( "Unknown environment variable %s in %s.",
00345                      envvar.c_str(), path.c_str() );
00346 
00347         expanded.replace(begvar, len+3, envpath);
00348 
00349         // Look for other environment variables
00350         begvar = expanded.find( "${" );
00351         endvar = expanded.find(  "}" );
00352     }
00353 
00354     // This method MUST NOT return a path since it would lead to an infinite
00355     // loop of ctors.
00356     return expanded;
00357 }
00358 
00359 
00361 // setCanonicalInErrors //
00363 bool PPath::canonical_in_errors = false;
00364 
00365 void PPath::setCanonicalInErrors(bool canonical)
00366 {
00367     PPath::canonical_in_errors = canonical;
00368 }
00369 
00370 #if defined(__CYGWIN__)
00371 
00372 extern "C" void cygwin_conv_to_win32_path(const char *path,
00373                                           char *win32_path);
00374 #endif
00375 
00377 // PPath methods
00378 
00379 PPath::PPath(const char* path)
00380     : _protocol("")
00381 {
00382     // MODULE_LOG << "PPath(const char* path = " << path << ")" << endl;
00383     operator=( PPath(string(path)) );
00384 }
00385 
00386 // The canonical path always contains '/' delimiters to seperate
00387 // subdirectories. Under windows, the internal representation will
00388 // however keep the '\' version. Under Unix, the following simply copy the
00389 // path value in the current instance.
00390 PPath::PPath(const string& path_)
00391     : _protocol("")
00392 {
00393     // MODULE_LOG << "PPath(const string& path_ = " << path_ << ")" << endl;
00394     
00395     // Empty path.
00396     if ( path_.empty() ) 
00397         return;
00398     const string* the_path = &path_;
00399 #if defined(__CYGWIN__) || defined(_MINGW_)
00400 #ifdef __CYGWIN__
00401     char buf[3000];
00402 #endif
00403     string new_path;
00404     // This is a hack to try to get the right DOS path from Cygwin.
00405     // Because Cygwin has its own translation rules, not necessarily compatible
00406     // with the PPath ones, we ask it to translate the path iff it starts with
00407     // a UNIX '/' character. TODO We will need a better home-made function
00408     // to translate paths safely.
00409     if (startsWith(*the_path, '/')) {
00410 #if defined(__CYGWIN__)
00411         cygwin_conv_to_win32_path(the_path->c_str(), buf);
00412         new_path = string(buf);
00413 #elif defined(_MINGW_)
00414         // We need to convert the path by ourselves.
00415         if (!startsWith(*the_path, "/cygdrive/")) {
00416             PLWARNING("Path '%s' is expected to start with '/cygdrive/'",
00417                     the_path->c_str());
00418             new_path = *the_path;
00419         } else {
00420             // Remove '/cygdrive'.
00421             new_path = the_path->substr(9);
00422             // Copy drive letter from second to first position.
00423             new_path[0] = new_path[1];
00424             // Add ':' after drive letter.
00425             new_path[1] = ':';
00426             // Replace '/' by '\'.
00427             for (string::size_type i = 0; i < new_path.size(); i++)
00428                 if (new_path[i] == '/')
00429                     new_path[i] = '\\';
00430         }
00431 #endif
00432         the_path = &new_path;
00433     }
00434 #endif
00435 
00436     // The path_ argument may contain environment variables that must be
00437     // expanded prior to any other processing.
00438     string internal =  expandEnvVariables( *the_path );
00439   
00440     // The PPath internal string is set here
00441     string::operator=   ( internal );
00442     resolveSlashChars   ( );
00443     expandMetaprotocols ( );
00444     resolveDots         ( );    
00445     parseProtocol       ( );
00446     // pout << "Creating PPath from '" << *the_path << "' --> '" << string(*this)
00447     //      << "'" << endl;
00448 }
00449 
00451 // resolveSlashChars //
00453 void PPath::resolveSlashChars( )
00454 {
00455     size_t plen = length();      
00456     string resolved;
00457     resolved.reserve( plen );
00458   
00459     bool last_is_slash = false;
00460     for ( size_t ch = 0; ch < plen; ch++ )
00461     {
00462         char char_ch = operator[](ch);
00463         if ( forbidden_chars().find(char_ch) != npos )
00464             PLERROR( "PPath '%s' cannot contain character '%c' (or any of \"%s\").",
00465                      c_str(), char_ch, forbidden_chars().c_str() );
00466     
00467         // Convert the canonic representation '/' to the appropriate
00468         // representation given the system (see slash_char instanciation in
00469         // PPath.cc). Multiple slash chars are removed.
00470         if ( char_ch == PPATH_SLASH || char_ch == _slash_char() )
00471         {
00472             if( !last_is_slash ) { // Prevents duplicated slash characters.
00473                 resolved += _slash_char();
00474                 last_is_slash = true;
00475             }
00476         }
00477         else {
00478             resolved += char_ch;
00479             last_is_slash = false;
00480         }
00481     }
00482 
00483     string::operator=(resolved);
00484 }
00485 
00487 // expandMetaprotocols //
00489 void PPath::expandMetaprotocols()
00490 {
00491     size_t endmeta = find(':');
00492     if ( endmeta != npos )    
00493     {
00494         string meta = substr(0, endmeta);
00495         map<string, PPath>::const_iterator it = metaprotocolToMetapath().find(meta);
00496 
00497         PPath metapath;
00498         if ( it != metaprotocolToMetapath().end() )
00499             metapath = it->second;
00500         else
00501             metapath = getenv(meta);
00502 
00503         if ( !metapath.isEmpty() )
00504         {      
00505             string after_colon = endmeta == length()-1 ? "" : substr(endmeta+1);
00506             *this = metapath / after_colon;
00507         }
00508     }
00509 }
00510 
00512 // resolveSingleDots //
00514 void PPath::resolveSingleDots() {
00515     static string ds, sd;
00516     static bool initialized = false;
00517     if (!initialized) {
00518         initialized = true;
00519         ds   = "."    + _slash();  // Posix: "./"    DOS: ".\" 
00520         sd   = _slash() + ".";     // Posix: "/."    DOS: "\."
00521     }
00522     // Examples with single dots.
00523     // ./foo      -> foo
00524     // ./         -> ./
00525     // .          -> .
00526     // /.         -> /
00527     // /./        -> /
00528     // /./foo     -> /foo
00529     // foo/.      -> foo
00530     // foo/./     -> foo/
00531     // foo/./bar  -> foo/bar
00532     // foo/.bar   -> foo/.bar
00533 
00534     // First deal with "/.":
00535     // - when it is followed by a slash, remove it
00536     // - when it ends the path, remove the dot, and remove the slash unless
00537     //   the resulting directory is a root directory
00538     size_t pos_sd = find(sd);
00539     size_t next;
00540     while (pos_sd != npos) {
00541         if (pos_sd + 2 < size()) {
00542             if (operator[](pos_sd + 2) == _slash_char()) {
00543                 // It is followed by a slash.
00544                 replace(pos_sd, 2, ""); // Remove '/.'
00545                 next = pos_sd;
00546             }
00547             else
00548                 next = pos_sd + 2;      // We ignore this one (e.g. "/.plearn").
00549         } else {
00550             // It ends the path.
00551             resize(size() - 1);       // Remove '.'
00552             if (!isRoot())
00553                 resize(size() - 1);     // Remove '/'
00554             next = size();            // We reached the end.
00555         }
00556         pos_sd = find(sd, next);
00557     }
00558 
00559     // Now deals with "./". Because we have removed the "/." before, we cannot
00560     // have "/./" nor "./.". Thus the only case we have to consider is when it
00561     // starts the path (and this can happen only once), in which case we remove
00562     // it iff there is something that follows.
00563     size_t pos_ds = find(ds);
00564     if (pos_ds == 0 && pos_ds + 2 < size())
00565         replace(0, 2, "");
00566 }
00567 
00569 // resolveDoubleDots //
00571 void PPath::resolveDoubleDots() {
00572     static string sdd;
00573     static bool initialized = false;
00574     if (!initialized) {
00575         initialized = true;
00576         sdd  = _slash() + "..";    // Posix: "/.."   DOS: "\.." 
00577     }
00578     // Examples with double dots.
00579     // "/.."         -> PLERROR
00580     // "/../foo"     -> PLERROR
00581     // "../foo"      -> "../foo"
00582     // "foo/.."      -> "."
00583     // "foo/../"     -> "./"
00584     // "/foo/.."     -> "/"
00585     // "/foo/../"    -> "/"
00586     // "foo/../bar"  -> "./bar" (call resolveSingleDots() after)
00587     // "/foo/../bar" -> "/bar"
00588     // "/..foo"      -> "/..foo"
00589     // "foo../"      -> "foo../"
00590     // "../../../foo"-> "../../../foo"
00591     // "foo/../../.."-> "foo/../../.."
00592 
00593     // We only care about "/.." when it is followed by a slash or it ends the path.
00594     // The path made of the substring until the '/' must not be a root directory.
00595     size_t pos_sdd = find(sdd);
00596     size_t next;
00597     while (pos_sdd != npos) {
00598         if (pos_sdd + 3 < size() && operator[](pos_sdd + 3) != _slash_char()) {
00599             // Something like "/..foo", that we ignore.
00600             next = pos_sdd + 4;
00601         } else {
00602             // Look for the slash just before.
00603             size_t pos_previous_slash = pos_sdd == 0 ? npos
00604                 : rfind(_slash_char(), pos_sdd - 1);
00605             if (pos_previous_slash == npos) {
00606                 // We need to make sure we are not trying to go up on a root
00607                 // directory.
00608                 if (PPath(substr(0, pos_sdd + 1)).isRoot())
00609                     // Long single-line error message to ensure tests pass.
00610                     PLERROR("In PPath::resolveDots - '%s' is invalid", errorDisplay().c_str());
00611                 if (   (pos_sdd == 2 && substr(0,2) == "..")
00612                        || (pos_sdd == 1 && operator[](0) == '.'))
00613                     // We are in the case "../.." or "./.."
00614                     next = pos_sdd + 3;
00615                 else {
00616                     // We are in the case "foo/.."
00617                     replace(0, pos_sdd + 3, ".");
00618                     next = 1;
00619                 }
00620             } else {
00621                 // There was a slash: "/xxx/..". If "xxx" == "..", we do nothing,
00622                 // otherwise we get rid of it.
00623                 if (substr(pos_previous_slash+1, 2) == "..")
00624                     next = pos_sdd + 3;
00625                 else {
00626                     // We must be careful with the special case "/foo/..", where we
00627                     // need to ensure we keep a final slash.
00628                     if (   pos_sdd + 3 == size()    // Ends with "/.."
00629                            && (   pos_previous_slash == 0
00630                                   || rfind(_slash_char(), pos_previous_slash-1) == npos) // "xxx/foo/.."
00631                            && PPath(substr(0, pos_previous_slash+1)).isRoot())  // "xxx/" is root
00632                         replace(pos_previous_slash + 1, pos_sdd-pos_previous_slash+2, "");
00633                     else
00634                         replace(pos_previous_slash, pos_sdd-pos_previous_slash+3, "");
00635                     next = pos_previous_slash;
00636                 }
00637             }
00638         }
00639         pos_sdd = find(sdd, next);
00640     }
00641     // At this point, we may have introduced an extra single dot ("./xxx").
00642     resolveSingleDots();
00643 }
00644 
00646 // resolveDots //
00648 void PPath::resolveDots()
00649 {
00650     // First remove temporarily the protocol.
00651     if (!_protocol.empty())
00652         replace(0, _protocol.size() + 1, "");
00653 
00654     resolveSingleDots();
00655     resolveDoubleDots();
00656 
00657     // Put back the protocol.
00658     if (!_protocol.empty())
00659         insert(0, _protocol + ":");
00660 }
00661 
00663 // parseProtocol //
00665 void PPath::parseProtocol()
00666 {
00667     size_t endpr = find(':');
00668   
00669     // No specified protocol  
00670     if ( endpr == npos )
00671     {
00672         // Even if the default protocol is considered to be the file protocol,
00673         // the _protocol member keeps the exact protocol value in the
00674         // string. The protocol() method, however, returns FILE_PROTOCOL if the
00675         // protocol was not specified.
00676         _protocol = "";
00677     }
00678 
00679     // The substring preceeding the ':' delimiter COULD be the protocol.  
00680     else
00681     {
00682         _protocol = substr(0, endpr);
00683 
00684         if ( _protocol == FILE_PROTOCOL ||
00685              _protocol == HTTP_PROTOCOL ||
00686              _protocol == FTP_PROTOCOL  )
00687         {
00688             // Make sure we do not define a protocol for a relative path.
00689             PPath check_filepath_validity = removeProtocol();
00690             if ( !check_filepath_validity.isEmpty() &&
00691                  !check_filepath_validity.isAbsPath() )
00692                 PLERROR("A PPath should not specify a protocol "
00693                         "for a relative path (in %s).", c_str());
00694         }
00695 
00696         // Nothing prevents a file from containing a ':' char. Under dos, for
00697         // instance, a letter preceeding ':' represents the drive. Hence, if
00698         // we do not recognize the protocol, we assume the ':' was part of
00699         // the file name.
00700         else
00701             _protocol = "";
00702     }
00703 }
00704 
00706 // absolute //
00708 PPath PPath::absolute(bool add_protocol) const
00709 {
00710     if (!add_protocol && protocol() != FILE_PROTOCOL)
00711         PLERROR("In PPath::absolute - The absolute() method is only meant for "
00712                 "the FILE_PROTOCOL protocol when 'add_protocol' is false");
00713 
00714     PPath abspath;
00715 
00716     // An empty path remains empty when converted to absolute.
00717     if ( isEmpty() || isAbsPath() )
00718         abspath = PPath( *this );
00719 
00720     // This is necessarily a file protocol (because other protocols require
00721     // an absolute path).
00722     // ===> we concatenate the current working directory of the process.
00723     else
00724     {
00725         PLASSERT( _protocol.empty() );
00726         abspath = PPath::getcwd() / *this;
00727     }
00728 
00729     // Remove useless trailing slash.
00730     abspath.removeTrailingSlash();
00731     // Add / remove protocol if required. Note that we cannot add a protocol to
00732     // an empty PPath (as an empty PPath is considered relative).
00733     if (add_protocol && !abspath.isEmpty())
00734         abspath = abspath.addProtocol();
00735     else
00736         // There can be a protocol in abspath only if there is one in *this.
00737         if (!_protocol.empty())
00738             abspath = abspath.removeProtocol();
00739 
00740     return abspath;
00741 }
00742 
00744 // canonical //
00746 string PPath::canonical() const
00747 {
00748     // An empty path does not need to be modified.
00749     if (isEmpty())
00750         return *this;
00751 
00752     // We have to replace any special path by its canonic equivalent.
00753     // Note that the protocol is kept. This means in particular that
00754     // if ppath = "/foo/bar" and the metaprotocol FOO maps to "file:/foo",
00755     // then the canonical form of ppath will still be "/foo/bar", and not
00756     // "FOO:bar" (we may want to change this behavior in the future).
00757 
00758     string canonic_path = *this;
00759     EXTREME_LOG << plhead("canonic_path: "+canonic_path) << endl;
00760 
00761     map<string, PPath>::const_iterator it  = metaprotocolToMetapath().begin();
00762     map<string, PPath>::const_iterator end = metaprotocolToMetapath().end();
00763 
00764     string metaprotocol;
00765     string metapath;      // Used to store the longest metapath found so far.
00766     while ( it != end )
00767     {      
00768         const string& candidate = it->second;
00769         if ( candidate.length() < metapath.length() )
00770         {
00771             // The candidate is shorter, we are not interested.
00772             EXTREME_LOG << "Shorter:\n\t"
00773                         << it->first << " -> " << candidate.c_str() << endl;
00774             ++it;
00775             continue;
00776         }
00777         if ( !startsWith(canonic_path, candidate) ) {
00778             // No match.
00779             EXTREME_LOG << "No match:\n\t"
00780                         << it->first << " -> " << candidate.c_str() << endl;
00781             ++it;
00782             continue;
00783         }
00784 
00785         size_t endpath = candidate.length();
00786 
00787         // The current candidate is only a subtring of the canonic path.
00788         // Ex:
00789         //    /home/dorionc/hey
00790         // in
00791         //    /home/dorionc/heyYou. 
00792         // Note that if the canonic path is a root directory, it may end
00793         // with a slash, in which case this cannot happen.
00794         if ( endpath != canonic_path.length()     &&
00795              canonic_path[endpath] != _slash_char() &&
00796              !endsWith(candidate, _slash_char()) )
00797         {
00798             EXTREME_LOG << "Substring:\n\t" 
00799                         << it->first << " -> " << it->second.c_str() << endl;
00800             ++it;
00801             continue;
00802         }
00803 
00804         // The current candidate is indeed a subpath of canonic_path.
00805         metaprotocol = it->first;
00806         metapath     = candidate;
00807         EXTREME_LOG << "Kept:\n\t" 
00808                     << it->first << " -> " << candidate.c_str() << endl;
00809         ++it; // We iterate to find the longest matching candidate.
00810     }
00811 
00812     // If any metapath was found, it must be replaced by its metaprotocol
00813     // equivalent.
00814     if ( metaprotocol.length() > 0 ) {
00815         canonic_path.replace( 0, metapath.length(), metaprotocol+':' );
00816         // Remove the slash just after the ':' if there is something following.
00817         size_t after_colon = metaprotocol.size() + 1;
00818         if (canonic_path.size() > after_colon + 1 &&
00819             canonic_path[after_colon] == _slash_char())
00820             canonic_path.erase(after_colon, 1);
00821     }
00822 
00823     // If necessary, convert slash characters to the canonical slash.
00824     if (_slash_char() != PPATH_SLASH) {
00825         size_t slash_pos = 0;
00826         while ((slash_pos = canonic_path.find(_slash_char(), slash_pos)) != npos)
00827             canonic_path[slash_pos] = PPATH_SLASH;
00828     }
00829 
00830     return canonic_path;
00831 }
00832 
00834 // errorDisplay //
00836 string PPath::errorDisplay() const
00837 {
00838     if (PPath::canonical_in_errors)
00839         return this->canonical();
00840     else
00841         return this->absolute();
00842 }
00843 
00845 // addProtocol //
00847 PPath PPath::addProtocol()  const
00848 {
00849     if ( _protocol.empty()) {
00850         if (!isAbsPath())
00851             // Ugly single-line error message to ensure tests pass.
00852             PLERROR("In PPath::addProtocol - A protocol can only be added to an absolute path, and '%s' is relative", errorDisplay().c_str());
00853         return ( PPath(string(FILE_PROTOCOL) + ':' + string(*this)) );
00854     }
00855     return PPath( *this );
00856 }
00857 
00859 // removeProtocol //
00861 PPath PPath::removeProtocol()  const
00862 {
00863     if ( _protocol.length()==0 )
00864         return PPath(*this);
00865     PPath no_protocol;
00866     // Avoid a call to the PPath constructor from a string.
00867     no_protocol.assign(substr(_protocol.length()+1));
00868     return no_protocol;
00869 }
00870 
00872 // operator/ //
00874 PPath PPath::operator/(const PPath& other) const
00875 {
00876     return ( PPath(*this) /= other );
00877 }
00878 
00880 // operator/= //
00882 PPath& PPath::operator/=(const PPath& other)
00883 {
00884     // MODULE_LOG << this->c_str() << " /= " << other << endl;
00885     
00886     if (other.isAbsPath())
00887         PLERROR("In PPath::operator/= - The concatenated path (%s) cannot be absolute",
00888                 other.c_str());
00889     // Add a slash if needed.
00890     // Note that 'other' cannot start with a slash, otherwise it would be an
00891     // absolute directory.
00892     if ( !isEmpty  ()                  &&
00893          !endsWith (*this, _slash_char()) )
00894         string::operator+=(_slash_char());
00895     string::operator+=(other);
00896 
00897     resolveDots       ( );
00898     return *this;
00899 }
00900 
00902 // operator== //
00904 bool PPath::operator== (const string& other) const
00905 {
00906     // MODULE_LOG << this->c_str() << " == " << other << " (string)"<< endl;    
00907     if ( other.empty() )
00908         return isEmpty();
00909     if ( isEmpty() )
00910         return false; // since 'other' is not
00911     return operator==( PPath(other) );
00912 }
00913 
00914 bool PPath::operator==(const PPath& other) const
00915 {
00916     // MODULE_LOG << this->c_str() << " == " << other << " (PPath)"<< endl;
00917     
00918     // If they are stricly equal there is no need to go further.
00919     // Otherwise they must point to the same absolute file or directory.
00920     // Note that the absolute() method already removes the trailing slash.
00921     return (   !strcmp(c_str(), other.c_str())
00922                || !strcmp(absolute(true).c_str(), other.absolute(true).c_str()));
00923 }
00924 
00926 // up //
00928 PPath PPath::up() const
00929 {
00930     if (isEmpty() || isRoot())
00931         // Note that the following line has more than 80 characters, but it is
00932         // the simplest way to avoid issues in tests, as the line number of
00933         // this error is displayed, and it may be ambiguous if it spans
00934         // multiple lines.
00935         PLERROR("In PPath::up - Cannot go up on directory '%s'", errorDisplay().c_str());
00936     return *this / "..";
00937 }
00938 
00940 // dirname //
00942 PPath PPath::dirname() const
00943 {
00944     if (isEmpty() || isRoot())
00945         return PPath(*this);
00946     size_t slash_pos = rfind(_slash_char());
00947     if ( slash_pos == npos ){
00948         if (_protocol.empty())
00949             return ".";  
00950         else
00951             return _protocol + ":.";
00952     }
00953     PPath result = substr(0, slash_pos + 1);
00954     // Remove trailing slash if it is not a root directory.
00955     result.removeTrailingSlash();
00956     return result;
00957 }
00958 
00960 // basename //
00962 PPath PPath::basename() const
00963 {
00964     size_t slash_pos = rfind(_slash_char());
00965     if ( slash_pos == npos )
00966         return PPath(*this);  
00967     return substr(slash_pos+1);
00968 }
00969 
00971 // hostname //
00973 string PPath::hostname() const
00974 {
00975     if (!isAbsPath())
00976         PLERROR("In PPath::hostname - Can only be used with an absolute path");
00977     if (isRoot())
00978         PLERROR("In PPath::hostname - The path cannot be a root directory");
00979     int i = 0;
00980     PPath paths[2];
00981     paths[0] = *this;
00982     paths[1] = paths[0].up();
00983     while (!paths[1 - i].isRoot()) {
00984         i = 1 - i;
00985         paths[1 - i] = paths[i].up();
00986     }
00987     paths[i].removeTrailingSlash();
00988     return paths[i].basename();
00989 }
00990 
00992 // extension //
00994 string PPath::extension(bool with_dot) const
00995 {
00996     PPath base = basename();
00997     size_t begext = base.rfind('.');
00998     if ( begext == npos            || // Not found.
00999          begext == base.length()-1 )  // Filename ending with a dot.
01000         return "";
01001     return with_dot ? base.substr(begext) : base.substr(begext+1);
01002 }
01003 
01005 // no_extension //
01007 PPath PPath::no_extension() const
01008 {
01009     size_t ext_size = extension().size();
01010     string copy(*this);
01011     if (ext_size > 0)
01012         copy.resize(size() - ext_size - 1);  // Remove the extension and the dot.
01013     return copy;
01014 }
01015 
01016 // // Returns a ppath shorter than 256 character and exempt of any of the
01017 // // following chars: "*?'\"${}[]@ ,()"  --- replaced by underscores. 
01018 // PPath PPath::makeFileNameValid(const PPath& path) const
01019 // {
01020 //   PPath  valid    = path;  
01021 //   PPath  dirname  = path.dirname();
01022 //   PPath  filename = path.basename();
01023 //   
01024 //   if ( filename.length() > 256 )
01025 //   {
01026 //     string ext             = filename.extension();
01027 // 
01028 //     int    kept_length     = 256-ext.length()-12;
01029 //     string filename_kept   = filename.substr( 0, kept_length );    
01030 // 
01031 //     int    rest_length     = filename.length() - kept_length - ext.length();
01032 //     string rest            = filename.substr( kept_length, rest_length );
01033 // 
01034 //     int j= 0;
01035 //     do
01036 //     {
01037 //       unsigned int n= j++;
01038 //       for(unsigned int i= 0; i < rest_length; ++i)
01039 //       {
01040 //         int m=0;
01041 //         switch(i%4)
01042 //         {
01043 //         case 3: m= 1; break;
01044 //         case 2: m= 256; break;
01045 //         case 1: m= 65536; break;
01046 //         case 0: m= 256*65536; break;
01047 //         }
01048 //         n+= m*(unsigned char)rest[i];
01049 //       }
01050 //       
01051 // 
01052 //       valid = dirname / filename_noext + "-" + tostring(n) + ext;
01053 //     }
01054 //     while( pathexists( valid ) );
01055 // 
01056 //     PLWARNING("makeFilenameValid: Filename '%s' changed to '%s'.", 
01057 //               path.c_str(), (dirname + filename_wo_ext + ext).c_str());
01058 //     valid = dirname + filename_wo_ext + ext;
01059 //   }
01060 //   
01061 //   // replace illegal characters
01062 //   char illegal[]="*?'\"${}[]@ ,()";
01063 //   for(int i=0;i<(signed)valid.size();i++)
01064 //     for(int j=0;j<15;j++)
01065 //       if(valid[i]==illegal[j])
01066 //         valid[i]='_';
01067 //   return valid;
01068 // }
01069 
01070 
01071 #if defined(WIN32)
01072 PPath PPath::drive() const
01073 {
01074     if ( find(':') == 1 && isalpha( c_str()[0] ) )
01075         return PPath(substr(0, 2));
01076     return PPath("");
01077 }
01078 
01079 bool PPath::isabs() const
01080 {
01081     // Note that a Win32 path is considered absolute if starting with a '\'
01082     // character: this is so that the path resulting from removing the protocol
01083     // in a ftp path for instance is still considered as absolute.
01084     return !drive().isEmpty() || isHttpPath() || isFtpPath() ||
01085            _protocol == FILE_PROTOCOL || startsWith(c_str(), _slash_char());
01086 }
01087 
01088 #else
01089 PPath PPath::drive() const
01090 {
01091     return PPath("");
01092 }
01093 
01094 bool PPath::isabs() const
01095 {
01096     return startsWith(c_str(), _slash_char()) || isHttpPath() || isFtpPath() || _protocol == FILE_PROTOCOL;
01097 }
01098 // TODO What about file:foo/bar ?
01099 
01100 #endif
01101 
01103 // isRoot //
01105 bool PPath::isRoot() const
01106 {
01107     if (!isAbsPath())
01108         return false;
01109     PPath no_prot = removeProtocol();
01110     string drv = no_prot.drive();
01111     return string(no_prot) == drv + _slash();
01112 }
01113 
01114 
01116 // parseUrlParameters //
01118 void PPath::parseUrlParameters(PPath& base_path, map<string, string>& parameters) const
01119 {
01120     size_t pos = rfind('?');
01121     if (pos == string::npos) {
01122         base_path = *this;
01123         return;
01124     }
01125     size_t check = rfind('?', pos - 1);
01126     if (check != string::npos)
01127         PLERROR("In PPath::parseUrlParameters - There can be only one '?' in a PPath");
01128     base_path = substr(0, pos);
01129     string rest = substr(pos + 1);
01130     vector<string> pairs = PLearn::split(rest, '&');
01131     string equal = "=";
01132     string name, value;
01133     vector<string>::const_iterator it = pairs.begin();
01134     for (; it != pairs.end(); it++) {
01135         PLearn::split_on_first(*it, equal, name, value);
01136         if (!name.empty()) {
01137             if (value.empty())
01138                 PLERROR("In PPath::parseUrlParameters - The parameter %s has no value",
01139                         name.c_str());
01140             parameters[name] = value;
01141         } else if (!value.empty())
01142             PLERROR("In PPath::parseUrlParameters - The value %s has no parameter name",
01143                     value.c_str());
01144     }
01145 }
01146 
01148 // removeTrailingSlash //
01150 void PPath::removeTrailingSlash() {
01151     if (isEmpty() || (*this)[length() - 1] != _slash_char() || isRoot())
01152         return;
01153     resize(length() - 1);
01154 }
01155 
01156 } // end of namespace PLearn
01157 
01158 
01159 /*
01160   Local Variables:
01161   mode:c++
01162   c-basic-offset:4
01163   c-file-style:"stroustrup"
01164   c-file-offsets:((innamespace . 0)(inline-open . 0))
01165   indent-tabs-mode:nil
01166   fill-column:79
01167   End:
01168 */
01169 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=79 :
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines