open_toontown_panda3d/pandatool/src/pandatoolbase/pathReplace.cxx

555 lines
15 KiB
C++

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file pathReplace.cxx
* @author drose
* @date 2003-02-07
*/
#include "pathReplace.h"
#include "config_putil.h"
#include "config_pandatoolbase.h"
#include "indent.h"
#include "virtualFileSystem.h"
/**
*
*/
PathReplace::
PathReplace() {
_path_store = PS_keep;
_copy_files = false;
_noabs = false;
_exists = false;
_error_flag = false;
}
/**
*
*/
PathReplace::
~PathReplace() {
}
/**
* Looks for a match for the given filename among all the replacement
* patterns, and returns the first match found. If additional_path is
* nonempty, it is an additional search path on which to look for the file.
* The model_path is always implicitly searched.
*/
Filename PathReplace::
match_path(const Filename &orig_filename,
const DSearchPath &additional_path) {
Filename match;
bool got_match = false;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Entries::const_iterator ei;
for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
const Entry &entry = (*ei);
Filename new_filename;
if (entry.try_match(orig_filename, new_filename)) {
// The prefix matches. Save the resulting filename for posterity.
got_match = true;
match = new_filename;
if (new_filename.is_fully_qualified()) {
// If the resulting filename is fully qualified, it's a match if and
// only if it exists.
if (vfs->exists(new_filename)) {
return new_filename;
}
} else {
// Otherwise, if it's a relative filename, attempt to look it up on
// the search path.
if (vfs->resolve_filename(new_filename, _path) ||
vfs->resolve_filename(new_filename, additional_path) ||
vfs->resolve_filename(new_filename, get_model_path())) {
// Found it!
if (_path_store == PS_keep) {
// If we asked to "keep" the pathname, we return the matched path,
// but not the found path.
return match;
} else {
// Otherwise, we return the actual, found path.
return new_filename;
}
}
}
// The prefix matched, but it didn't exist. Keep looking.
}
}
// The file couldn't be found anywhere. Did we at least get any prefix
// match?
if (got_match) {
if (_exists) {
_error_flag = true;
pandatoolbase_cat.error()
<< "File does not exist: " << match << "\n";
} else if (pandatoolbase_cat.is_debug()) {
pandatoolbase_cat.debug()
<< "File does not exist: " << match << "\n";
}
return match;
}
if (!orig_filename.is_local()) {
// Ok, we didn't match any specified prefixes. If the file is an absolute
// pathname and we have _noabs set, that's an error.
if (_noabs) {
_error_flag = true;
pandatoolbase_cat.error()
<< "Absolute pathname: " << orig_filename << "\n";
} else if (pandatoolbase_cat.is_debug()) {
pandatoolbase_cat.debug()
<< "Absolute pathname: " << orig_filename << "\n";
}
}
// Well, we still haven't found it; look it up on the search path as is.
if (_path_store != PS_keep) {
Filename new_filename = orig_filename;
if (vfs->resolve_filename(new_filename, _path) ||
vfs->resolve_filename(new_filename, additional_path) ||
vfs->resolve_filename(new_filename, get_model_path())) {
// Found it!
return new_filename;
}
}
// Nope, couldn't find anything. This is an error, but just return the
// original filename.
if (_exists) {
_error_flag = true;
pandatoolbase_cat.error()
<< "File does not exist: " << orig_filename << "\n";
} else if (pandatoolbase_cat.is_debug()) {
pandatoolbase_cat.debug()
<< "File does not exist: " << orig_filename << "\n";
}
return orig_filename;
}
/**
* Given a path to an existing filename, converts it as specified in the
* _path_store and or _path_directory properties to a form suitable for
* storing in an output file.
*/
Filename PathReplace::
store_path(const Filename &orig_filename) {
if (orig_filename.empty()) {
return orig_filename;
}
if (_path_directory.is_local()) {
_path_directory.make_absolute();
}
Filename filename = orig_filename;
if (_copy_files) {
copy_this_file(filename);
}
switch (_path_store) {
case PS_relative:
filename.make_absolute();
filename.make_relative_to(_path_directory);
break;
case PS_absolute:
filename.make_absolute();
break;
case PS_rel_abs:
filename.make_absolute();
filename.make_relative_to(_path_directory, false);
break;
case PS_strip:
filename = filename.get_basename();
break;
case PS_keep:
break;
case PS_invalid:
break;
}
return filename;
}
/**
* Converts the input path into two different forms: A resolved path, and an
* output path. The resolved path is an absolute path if at all possible.
* The output path is in the form specified by the -ps path store option.
*/
void PathReplace::
full_convert_path(const Filename &orig_filename,
const DSearchPath &additional_path,
Filename &resolved_path,
Filename &output_path) {
if (_path_directory.is_local()) {
_path_directory.make_absolute();
}
Filename match;
bool got_match = false;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
Entries::const_iterator ei;
for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
const Entry &entry = (*ei);
Filename new_filename;
if (entry.try_match(orig_filename, new_filename)) {
// The prefix matches. Save the resulting filename for posterity.
got_match = true;
match = new_filename;
if (new_filename.is_fully_qualified()) {
// If the resulting filename is fully qualified, it's a match if and
// only if it exists.
if (vfs->exists(new_filename)) {
resolved_path = new_filename;
goto calculate_output_path;
}
} else {
// Otherwise, if it's a relative filename, attempt to look it up on
// the search path.
if (vfs->resolve_filename(new_filename, _path) ||
vfs->resolve_filename(new_filename, additional_path) ||
vfs->resolve_filename(new_filename, get_model_path())) {
// Found it!
resolved_path = new_filename;
goto calculate_output_path;
}
}
// The prefix matched, but it didn't exist. Keep looking.
}
}
// The file couldn't be found anywhere. Did we at least get any prefix
// match?
if (got_match) {
if (_exists) {
_error_flag = true;
pandatoolbase_cat.error()
<< "File does not exist: " << match << "\n";
} else if (pandatoolbase_cat.is_debug()) {
pandatoolbase_cat.debug()
<< "File does not exist: " << match << "\n";
}
resolved_path = match;
goto calculate_output_path;
}
if (!orig_filename.is_local()) {
// Ok, we didn't match any specified prefixes. If the file is an absolute
// pathname and we have _noabs set, that's an error.
if (_noabs) {
_error_flag = true;
pandatoolbase_cat.error()
<< "Absolute pathname: " << orig_filename << "\n";
} else if (pandatoolbase_cat.is_debug()) {
pandatoolbase_cat.debug()
<< "Absolute pathname: " << orig_filename << "\n";
}
}
// Well, we still haven't found it; look it up on the search path as is.
{
Filename new_filename = orig_filename;
if (vfs->resolve_filename(new_filename, _path) ||
vfs->resolve_filename(new_filename, additional_path) ||
vfs->resolve_filename(new_filename, get_model_path())) {
// Found it!
match = orig_filename;
resolved_path = new_filename;
goto calculate_output_path;
}
}
// Nope, couldn't find anything. This is an error, but just return the
// original filename.
if (_exists) {
_error_flag = true;
pandatoolbase_cat.error()
<< "File does not exist: " << orig_filename << "\n";
} else if (pandatoolbase_cat.is_debug()) {
pandatoolbase_cat.debug()
<< "File does not exist: " << orig_filename << "\n";
}
match = orig_filename;
resolved_path = orig_filename;
// To calculate the output path, we need two inputs: the match, and the
// resolved path. Which one is used depends upon the path-store mode.
calculate_output_path:
if (_copy_files) {
if (copy_this_file(resolved_path)) {
match = resolved_path;
}
}
switch (_path_store) {
case PS_relative:
if (resolved_path.empty())
output_path = resolved_path;
else {
output_path = resolved_path;
output_path.make_absolute();
output_path.make_relative_to(_path_directory);
}
break;
case PS_absolute:
if (resolved_path.empty())
output_path = resolved_path;
else {
output_path = resolved_path;
output_path.make_absolute();
}
break;
case PS_rel_abs:
if (resolved_path.empty())
output_path = resolved_path;
else {
output_path = resolved_path;
output_path.make_absolute();
output_path.make_relative_to(_path_directory, false);
}
break;
case PS_strip:
output_path = match.get_basename();
break;
case PS_keep:
output_path = match;
break;
case PS_invalid:
output_path = "";
break;
}
}
/**
*
*/
void PathReplace::
write(std::ostream &out, int indent_level) const {
Entries::const_iterator ei;
for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
indent(out, indent_level)
<< "-pr " << (*ei)._orig_prefix << "="
<< (*ei)._replacement_prefix << "\n";
}
int num_directories = _path.get_num_directories();
for (int i = 0; i < num_directories; i++) {
indent(out, indent_level)
<< "-pp " << _path.get_directory(i) << "\n";
}
indent(out, indent_level)
<< "-ps " << _path_store << "\n";
// The path directory is only relevant if _path_store is rel or rel_abs.
switch (_path_store) {
case PS_relative:
case PS_rel_abs:
indent(out, indent_level)
<< "-pd " << _path_directory << "\n";
default:
break;
}
if (_copy_files) {
indent(out, indent_level)
<< "-pc " << _copy_into_directory << "\n";
}
if (_noabs) {
indent(out, indent_level)
<< "-noabs\n";
}
}
/**
* Copies the indicated file into the copy_into_directory, and adjusts
* filename to reference the new location. Returns true if the copy is made
* and the filename is changed, false otherwise.
*/
bool PathReplace::
copy_this_file(Filename &filename) {
if (_copy_into_directory.is_local()) {
_copy_into_directory = Filename(_path_directory, _copy_into_directory);
}
Copied::iterator ci = _orig_to_target.find(filename);
if (ci != _orig_to_target.end()) {
// This file has already been successfully copied, so we can quietly
// return its new target filename.
if (filename != (*ci).second) {
filename = (*ci).second;
return true;
}
return false;
}
Filename target_filename(_copy_into_directory, filename.get_basename());
ci = _target_to_orig.find(target_filename);
if (ci != _target_to_orig.end()) {
if ((*ci).second != filename) {
_error_flag = true;
pandatoolbase_cat.error()
<< "Filename conflict! Both " << (*ci).second << " and "
<< filename << " map to " << target_filename << "\n";
}
// Don't copy this one.
_orig_to_target[filename] = filename;
return false;
}
_orig_to_target[filename] = target_filename;
_target_to_orig[target_filename] = filename;
// Make the copy.
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->make_directory_full(_copy_into_directory);
if (!vfs->copy_file(filename, target_filename)) {
_error_flag = true;
pandatoolbase_cat.error()
<< "Cannot copy file from " << filename << " to " << target_filename
<< "\n";
_orig_to_target[filename] = filename;
return false;
}
filename = target_filename;
return true;
}
/**
*
*/
PathReplace::Entry::
Entry(const std::string &orig_prefix, const std::string &replacement_prefix) :
_orig_prefix(orig_prefix),
_replacement_prefix(replacement_prefix)
{
// Eliminate trailing slashes; they're implicit.
if (_orig_prefix.length() > 1 &&
_orig_prefix[_orig_prefix.length() - 1] == '/') {
_orig_prefix = _orig_prefix.substr(0, _orig_prefix.length() - 1);
}
if (_replacement_prefix.length() > 1 &&
_replacement_prefix[_replacement_prefix.length() - 1] == '/') {
_replacement_prefix = _replacement_prefix.substr(0, _replacement_prefix.length() - 1);
}
Filename filename(_orig_prefix);
_is_local = filename.is_local();
vector_string components;
filename.extract_components(components);
vector_string::const_iterator ci;
for (ci = components.begin(); ci != components.end(); ++ci) {
_orig_components.push_back(Component(*ci));
}
}
/**
* Considers whether the indicated filename matches this entry's prefix. If
* so, switches the prefix and stores the result in new_filename, and returns
* true; otherwise, returns false.
*/
bool PathReplace::Entry::
try_match(const Filename &filename, Filename &new_filename) const {
if (_is_local != filename.is_local()) {
return false;
}
vector_string components;
filename.extract_components(components);
size_t mi = r_try_match(components, 0, 0);
if (mi == 0) {
// Sorry, no match.
return false;
}
// We found a match. Construct the replacement string.
std::string result = _replacement_prefix;
while (mi < components.size()) {
if (!result.empty()) {
result += '/';
}
result += components[mi];
++mi;
}
new_filename = result;
return true;
}
/**
* The recursive implementation of try_match(). Actually, this is doubly-
* recursive, to implement the "**" feature.
*
* The return value is the number of the "components" vector that successfully
* matched against all of the orig_components. (It's a variable number
* because there might be one or more "**" entries.)
*/
size_t PathReplace::Entry::
r_try_match(const vector_string &components, size_t oi, size_t ci) const {
if (oi >= _orig_components.size()) {
// If we ran out of user-supplied components, we're done.
return ci;
}
if (ci >= components.size()) {
// If we reached the end of the string, but we still have user-supplied
// components, we failed. (Arguably there should be a special case here
// for a user-supplied string that ends in "**", but I don't think the
// user ever wants to match the complete string.)
return 0;
}
const Component &orig_component = _orig_components[oi];
if (orig_component._double_star) {
// If we have a double star, first consider the match if it were expanded
// as far as possible.
size_t mi = r_try_match(components, oi, ci + 1);
if (mi != 0) {
return mi;
}
// Then try the match as if it there were no double star entry.
return r_try_match(components, oi + 1, ci);
}
// We don't have a double star, it's just a one-for-one component entry.
// Does it match?
if (orig_component._orig_prefix.matches(components[ci])) {
// It does! Keep going.
return r_try_match(components, oi + 1, ci + 1);
}
// It doesn't match, sorry.
return 0;
}