Panda3D

pathReplace.cxx

00001 // Filename: pathReplace.cxx
00002 // Created by:  drose (07Feb03)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "pathReplace.h"
00016 #include "config_util.h"
00017 #include "config_pandatoolbase.h"
00018 #include "indent.h"
00019 #include "virtualFileSystem.h"
00020 
00021 ////////////////////////////////////////////////////////////////////
00022 //     Function: PathReplace::Constructor
00023 //       Access: Public
00024 //  Description:
00025 ////////////////////////////////////////////////////////////////////
00026 PathReplace::
00027 PathReplace() {
00028   _path_store = PS_keep;
00029   _noabs = false;
00030   _exists = false;
00031   _error_flag = false;
00032 }
00033 
00034 ////////////////////////////////////////////////////////////////////
00035 //     Function: PathReplace::Destructor
00036 //       Access: Public
00037 //  Description:
00038 ////////////////////////////////////////////////////////////////////
00039 PathReplace::
00040 ~PathReplace() {
00041 }
00042 
00043 ////////////////////////////////////////////////////////////////////
00044 //     Function: PathReplace::match_path
00045 //       Access: Public
00046 //  Description: Looks for a match for the given filename among all
00047 //               the replacement patterns, and returns the first match
00048 //               found.  If additional_path is nonempty, it is an
00049 //               additional search path on which to look for the file.
00050 //               The model_path is always implicitly searched.
00051 ////////////////////////////////////////////////////////////////////
00052 Filename PathReplace::
00053 match_path(const Filename &orig_filename, 
00054            const DSearchPath &additional_path) {
00055   Filename match;
00056   bool got_match = false;
00057 
00058   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00059 
00060   Entries::const_iterator ei;
00061   for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
00062     const Entry &entry = (*ei);
00063     Filename new_filename;
00064     if (entry.try_match(orig_filename, new_filename)) {
00065       // The prefix matches.  Save the resulting filename for
00066       // posterity.
00067       got_match = true;
00068       match = new_filename;
00069       
00070       if (new_filename.is_fully_qualified()) {
00071         // If the resulting filename is fully qualified, it's a match
00072         // if and only if it exists.
00073         if (vfs->exists(new_filename)) {
00074           return new_filename;
00075         }
00076         
00077       } else {
00078         // Otherwise, if it's a relative filename, attempt to look it
00079         // up on the search path.
00080         if (vfs->resolve_filename(new_filename, _path) ||
00081             vfs->resolve_filename(new_filename, additional_path) ||
00082             vfs->resolve_filename(new_filename, get_model_path())) {
00083           // Found it!
00084           if (_path_store == PS_keep) {
00085             // If we asked to "keep" the pathname, we return the
00086             // matched path, but not the found path.
00087             return match;
00088           } else {
00089             // Otherwise, we return the actual, found path.
00090             return new_filename;
00091           }
00092         }
00093       }
00094       
00095       // The prefix matched, but it didn't exist.  Keep looking.
00096     }
00097   }
00098 
00099   // The file couldn't be found anywhere.  Did we at least get any
00100   // prefix match?
00101   if (got_match) {
00102     if (_exists) {
00103       _error_flag = true;
00104       pandatoolbase_cat.error()
00105         << "File does not exist: " << match << "\n";
00106     } else if (pandatoolbase_cat.is_debug()) {
00107       pandatoolbase_cat.debug()
00108         << "File does not exist: " << match << "\n";
00109     }
00110 
00111     return match;
00112   }
00113 
00114   if (!orig_filename.is_local()) {
00115     // Ok, we didn't match any specified prefixes.  If the file is an
00116     // absolute pathname and we have _noabs set, that's an error.
00117     if (_noabs) {
00118       _error_flag = true;
00119       pandatoolbase_cat.error()
00120         << "Absolute pathname: " << orig_filename << "\n";
00121     } else if (pandatoolbase_cat.is_debug()) {
00122       pandatoolbase_cat.debug()
00123         << "Absolute pathname: " << orig_filename << "\n";
00124     }
00125   }
00126 
00127   // Well, we still haven't found it; look it up on the search path as
00128   // is.
00129   if (_path_store != PS_keep) {
00130     Filename new_filename = orig_filename;
00131     if (vfs->resolve_filename(new_filename, _path) ||
00132         vfs->resolve_filename(new_filename, additional_path) ||
00133         vfs->resolve_filename(new_filename, get_model_path())) {
00134       // Found it!
00135       return new_filename;
00136     }
00137   }
00138 
00139   // Nope, couldn't find anything.  This is an error, but just return
00140   // the original filename.
00141   if (_exists) {
00142     _error_flag = true;
00143     pandatoolbase_cat.error()
00144       << "File does not exist: " << orig_filename << "\n";
00145   } else if (pandatoolbase_cat.is_debug()) {
00146     pandatoolbase_cat.debug()
00147       << "File does not exist: " << orig_filename << "\n";
00148   }
00149   return orig_filename;
00150 }
00151 
00152 ////////////////////////////////////////////////////////////////////
00153 //     Function: PathReplace::store_path
00154 //       Access: Public
00155 //  Description: Given a path to an existing filename, converts it as
00156 //               specified in the _path_store and or _path_directory
00157 //               properties to a form suitable for storing in an
00158 //               output file.
00159 ////////////////////////////////////////////////////////////////////
00160 Filename PathReplace::
00161 store_path(const Filename &orig_filename) {
00162   if (orig_filename.empty()) {
00163     return orig_filename;
00164   }
00165 
00166   if (_path_directory.is_local()) {
00167     _path_directory.make_absolute();
00168   }
00169   Filename filename = orig_filename;
00170 
00171   switch (_path_store) {
00172   case PS_relative:
00173     filename.make_absolute();
00174     filename.make_relative_to(_path_directory);
00175     break;
00176 
00177   case PS_absolute:
00178     filename.make_absolute();
00179     break;
00180 
00181   case PS_rel_abs:
00182     filename.make_absolute();
00183     filename.make_relative_to(_path_directory, false);
00184     break;
00185 
00186   case PS_strip:
00187     filename = filename.get_basename();
00188     break;
00189 
00190   case PS_keep:
00191     break;
00192 
00193   case PS_invalid:
00194     break;
00195   }
00196 
00197   return filename;
00198 }
00199 
00200 ////////////////////////////////////////////////////////////////////
00201 //     Function: PathReplace::full_convert_path
00202 //       Access: Public
00203 //  Description: Converts the input path into two different forms:
00204 //               A resolved path, and an output path.  The resolved
00205 //               path is an absolute path if at all possible.  The
00206 //               output path is in the form specified by the -ps
00207 //               path store option.
00208 ////////////////////////////////////////////////////////////////////
00209 void PathReplace::
00210 full_convert_path(const Filename &orig_filename, 
00211                   const DSearchPath &additional_path,
00212                   Filename &resolved_path,
00213                   Filename &output_path) {
00214   Filename match;
00215   bool got_match = false;
00216 
00217   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00218 
00219   Entries::const_iterator ei;
00220   for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
00221     const Entry &entry = (*ei);
00222     Filename new_filename;
00223     if (entry.try_match(orig_filename, new_filename)) {
00224       // The prefix matches.  Save the resulting filename for
00225       // posterity.
00226       got_match = true;
00227       match = new_filename;
00228       
00229       if (new_filename.is_fully_qualified()) {
00230         // If the resulting filename is fully qualified, it's a match
00231         // if and only if it exists.
00232         if (vfs->exists(new_filename)) {
00233           resolved_path = new_filename;
00234           goto calculate_output_path;
00235         }
00236         
00237       } else {
00238         // Otherwise, if it's a relative filename, attempt to look it
00239         // up on the search path.
00240         if (vfs->resolve_filename(new_filename, _path) ||
00241             vfs->resolve_filename(new_filename, additional_path) ||
00242             vfs->resolve_filename(new_filename, get_model_path())) {
00243           // Found it!
00244           resolved_path = new_filename;
00245           goto calculate_output_path;
00246         }
00247       }
00248       
00249       // The prefix matched, but it didn't exist.  Keep looking.
00250     }
00251   }
00252 
00253   // The file couldn't be found anywhere.  Did we at least get any
00254   // prefix match?
00255   if (got_match) {
00256     if (_exists) {
00257       _error_flag = true;
00258       pandatoolbase_cat.error()
00259         << "File does not exist: " << match << "\n";
00260     } else if (pandatoolbase_cat.is_debug()) {
00261       pandatoolbase_cat.debug()
00262         << "File does not exist: " << match << "\n";
00263     }
00264 
00265     resolved_path = match;
00266     goto calculate_output_path;
00267   }
00268 
00269   if (!orig_filename.is_local()) {
00270     // Ok, we didn't match any specified prefixes.  If the file is an
00271     // absolute pathname and we have _noabs set, that's an error.
00272     if (_noabs) {
00273       _error_flag = true;
00274       pandatoolbase_cat.error()
00275         << "Absolute pathname: " << orig_filename << "\n";
00276     } else if (pandatoolbase_cat.is_debug()) {
00277       pandatoolbase_cat.debug()
00278         << "Absolute pathname: " << orig_filename << "\n";
00279     }
00280   }
00281 
00282   // Well, we still haven't found it; look it up on the search path as
00283   // is.
00284   {
00285     Filename new_filename = orig_filename;
00286     if (vfs->resolve_filename(new_filename, _path) ||
00287         vfs->resolve_filename(new_filename, additional_path) ||
00288         vfs->resolve_filename(new_filename, get_model_path())) {
00289       // Found it!
00290       match = orig_filename;
00291       resolved_path = new_filename;
00292       goto calculate_output_path;
00293     }
00294   }
00295 
00296   // Nope, couldn't find anything.  This is an error, but just return
00297   // the original filename.
00298   if (_exists) {
00299     _error_flag = true;
00300     pandatoolbase_cat.error()
00301       << "File does not exist: " << orig_filename << "\n";
00302   } else if (pandatoolbase_cat.is_debug()) {
00303     pandatoolbase_cat.debug()
00304       << "File does not exist: " << orig_filename << "\n";
00305   }
00306   match = orig_filename;
00307   resolved_path = orig_filename;
00308 
00309   // To calculate the output path, we need two inputs:
00310   // the match, and the resolved path.  Which one is used
00311   // depends upon the path-store mode.
00312  calculate_output_path:
00313 
00314   switch (_path_store) {
00315   case PS_relative:
00316     if (resolved_path.empty())
00317       output_path = resolved_path;
00318     else {
00319       if (_path_directory.is_local())
00320         _path_directory.make_absolute();
00321       output_path = resolved_path;
00322       output_path.make_absolute();
00323       output_path.make_relative_to(_path_directory);
00324     }
00325     break;
00326     
00327   case PS_absolute:
00328     if (resolved_path.empty())
00329       output_path = resolved_path;
00330     else {
00331       output_path = resolved_path;
00332       output_path.make_absolute();
00333     }
00334     break;
00335 
00336   case PS_rel_abs:
00337     if (resolved_path.empty())
00338       output_path = resolved_path;
00339     else {
00340       if (_path_directory.is_local())
00341         _path_directory.make_absolute();
00342       output_path = resolved_path;
00343       output_path.make_absolute();
00344       output_path.make_relative_to(_path_directory, false);
00345     }
00346     break;
00347 
00348   case PS_strip:
00349     output_path = match.get_basename();
00350     break;
00351 
00352   case PS_keep:
00353     output_path = match;
00354     break;
00355 
00356   case PS_invalid:
00357     output_path = "";
00358     break;
00359   }
00360 }
00361 
00362 ////////////////////////////////////////////////////////////////////
00363 //     Function: PathReplace::write
00364 //       Access: Public
00365 //  Description: 
00366 ////////////////////////////////////////////////////////////////////
00367 void PathReplace::
00368 write(ostream &out, int indent_level) const {
00369   Entries::const_iterator ei;
00370   for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
00371     indent(out, indent_level)
00372       << "-pr " << (*ei)._orig_prefix << "=" 
00373       << (*ei)._replacement_prefix << "\n";
00374   }
00375   int num_directories = _path.get_num_directories();
00376   for (int i = 0; i < num_directories; i++) {
00377     indent(out, indent_level)
00378       << "-pp " << _path.get_directory(i) << "\n";
00379   }
00380   indent(out, indent_level)
00381     << "-ps " << _path_store << "\n";
00382 
00383   // The path directory is only relevant if _path_store is rel or rel_abs.
00384   switch (_path_store) {
00385   case PS_relative:
00386   case PS_rel_abs:
00387     indent(out, indent_level)
00388       << "-pd " << _path_directory << "\n";
00389 
00390   default:
00391     break;
00392   }
00393 
00394   if (_noabs) {
00395     indent(out, indent_level)
00396       << "-noabs\n";
00397   }
00398 }
00399 
00400 ////////////////////////////////////////////////////////////////////
00401 //     Function: PathReplace::Entry::Constructor
00402 //       Access: Public
00403 //  Description: 
00404 ////////////////////////////////////////////////////////////////////
00405 PathReplace::Entry::
00406 Entry(const string &orig_prefix, const string &replacement_prefix) :
00407   _orig_prefix(orig_prefix),
00408   _replacement_prefix(replacement_prefix)
00409 {
00410   // Eliminate trailing slashes; they're implicit.
00411   if (_orig_prefix.length() > 1 &&
00412       _orig_prefix[_orig_prefix.length() - 1] == '/') {
00413     _orig_prefix = _orig_prefix.substr(0, _orig_prefix.length() - 1);
00414   }
00415   if (_replacement_prefix.length() > 1 &&
00416       _replacement_prefix[_replacement_prefix.length() - 1] == '/') {
00417     _replacement_prefix = _replacement_prefix.substr(0, _replacement_prefix.length() - 1);
00418   }
00419 
00420   Filename filename(_orig_prefix);
00421   _is_local = filename.is_local();
00422 
00423   vector_string components;
00424   filename.extract_components(components);
00425   vector_string::const_iterator ci;
00426   for (ci = components.begin(); ci != components.end(); ++ci) {
00427     _orig_components.push_back(Component(*ci));
00428   }
00429 }
00430 
00431 ////////////////////////////////////////////////////////////////////
00432 //     Function: PathReplace::Entry::try_match
00433 //       Access: Public
00434 //  Description: Considers whether the indicated filename matches
00435 //               this entry's prefix.  If so, switches the prefix and
00436 //               stores the result in new_filename, and returns true;
00437 //               otherwise, returns false.
00438 ////////////////////////////////////////////////////////////////////
00439 bool PathReplace::Entry::
00440 try_match(const Filename &filename, Filename &new_filename) const {
00441   if (_is_local != filename.is_local()) {
00442     return false;
00443   }
00444   vector_string components;
00445   filename.extract_components(components);
00446   size_t mi = r_try_match(components, 0, 0);
00447   if (mi == 0) {
00448     // Sorry, no match.
00449     return false;
00450   }
00451 
00452   // We found a match.  Construct the replacement string.
00453   string result = _replacement_prefix;
00454   while (mi < components.size()) {
00455     if (!result.empty()) {
00456       result += '/';
00457     }
00458     result += components[mi];
00459     ++mi;
00460   }
00461   new_filename = result;
00462   return true;
00463 }
00464 
00465 ////////////////////////////////////////////////////////////////////
00466 //     Function: PathReplace::Entry::r_try_match
00467 //       Access: Public
00468 //  Description: The recursive implementation of try_match().
00469 //               Actually, this is doubly-recursive, to implement the
00470 //               "**" feature.
00471 //
00472 //               The return value is the number of the "components"
00473 //               vector that successfully matched against all of the
00474 //               orig_components.  (It's a variable number because
00475 //               there might be one or more "**" entries.)
00476 ////////////////////////////////////////////////////////////////////
00477 size_t PathReplace::Entry::
00478 r_try_match(const vector_string &components, size_t oi, size_t ci) const {
00479   if (oi >= _orig_components.size()) {
00480     // If we ran out of user-supplied components, we're done.
00481     return ci;
00482   }
00483   if (ci >= components.size()) {
00484     // If we reached the end of the string, but we still have
00485     // user-supplied components, we failed.  (Arguably there should be
00486     // a special case here for a user-supplied string that ends in
00487     // "**", but I don't think the user ever wants to match the
00488     // complete string.)
00489     return 0;
00490   }
00491 
00492   const Component &orig_component = _orig_components[oi];
00493   if (orig_component._double_star) {
00494     // If we have a double star, first consider the match if it were
00495     // expanded as far as possible.
00496     size_t mi = r_try_match(components, oi, ci + 1);
00497     if (mi != 0) {
00498       return mi;
00499     }
00500 
00501     // Then try the match as if it there were no double star entry.
00502     return r_try_match(components, oi + 1, ci);
00503   }
00504 
00505   // We don't have a double star, it's just a one-for-one component
00506   // entry.  Does it match?
00507   if (orig_component._orig_prefix.matches(components[ci])) {
00508     // It does!  Keep going.
00509     return r_try_match(components, oi + 1, ci + 1);
00510   }
00511 
00512   // It doesn't match, sorry.
00513   return 0;
00514 }
 All Classes Functions Variables Enumerations