Panda3D
|
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 }