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