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