Panda3D
pathReplace.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file pathReplace.cxx
10  * @author drose
11  * @date 2003-02-07
12  */
13 
14 #include "pathReplace.h"
15 #include "config_putil.h"
16 #include "config_pandatoolbase.h"
17 #include "indent.h"
18 #include "virtualFileSystem.h"
19 
20 /**
21  *
22  */
23 PathReplace::
24 PathReplace() {
25  _path_store = PS_keep;
26  _copy_files = false;
27  _noabs = false;
28  _exists = false;
29  _error_flag = false;
30 }
31 
32 /**
33  *
34  */
35 PathReplace::
36 ~PathReplace() {
37 }
38 
39 /**
40  * Looks for a match for the given filename among all the replacement
41  * patterns, and returns the first match found. If additional_path is
42  * nonempty, it is an additional search path on which to look for the file.
43  * The model_path is always implicitly searched.
44  */
46 match_path(const Filename &orig_filename,
47  const DSearchPath &additional_path) {
48  Filename match;
49  bool got_match = false;
50 
52 
53  Entries::const_iterator ei;
54  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
55  const Entry &entry = (*ei);
56  Filename new_filename;
57  if (entry.try_match(orig_filename, new_filename)) {
58  // The prefix matches. Save the resulting filename for posterity.
59  got_match = true;
60  match = new_filename;
61 
62  if (new_filename.is_fully_qualified()) {
63  // If the resulting filename is fully qualified, it's a match if and
64  // only if it exists.
65  if (vfs->exists(new_filename)) {
66  return new_filename;
67  }
68 
69  } else {
70  // Otherwise, if it's a relative filename, attempt to look it up on
71  // the search path.
72  if (vfs->resolve_filename(new_filename, _path) ||
73  vfs->resolve_filename(new_filename, additional_path) ||
74  vfs->resolve_filename(new_filename, get_model_path())) {
75  // Found it!
76  if (_path_store == PS_keep) {
77  // If we asked to "keep" the pathname, we return the matched path,
78  // but not the found path.
79  return match;
80  } else {
81  // Otherwise, we return the actual, found path.
82  return new_filename;
83  }
84  }
85  }
86 
87  // The prefix matched, but it didn't exist. Keep looking.
88  }
89  }
90 
91  // The file couldn't be found anywhere. Did we at least get any prefix
92  // match?
93  if (got_match) {
94  if (_exists) {
95  _error_flag = true;
96  pandatoolbase_cat.error()
97  << "File does not exist: " << match << "\n";
98  } else if (pandatoolbase_cat.is_debug()) {
99  pandatoolbase_cat.debug()
100  << "File does not exist: " << match << "\n";
101  }
102 
103  return match;
104  }
105 
106  if (!orig_filename.is_local()) {
107  // Ok, we didn't match any specified prefixes. If the file is an absolute
108  // pathname and we have _noabs set, that's an error.
109  if (_noabs) {
110  _error_flag = true;
111  pandatoolbase_cat.error()
112  << "Absolute pathname: " << orig_filename << "\n";
113  } else if (pandatoolbase_cat.is_debug()) {
114  pandatoolbase_cat.debug()
115  << "Absolute pathname: " << orig_filename << "\n";
116  }
117  }
118 
119  // Well, we still haven't found it; look it up on the search path as is.
120  if (_path_store != PS_keep) {
121  Filename new_filename = orig_filename;
122  if (vfs->resolve_filename(new_filename, _path) ||
123  vfs->resolve_filename(new_filename, additional_path) ||
124  vfs->resolve_filename(new_filename, get_model_path())) {
125  // Found it!
126  return new_filename;
127  }
128  }
129 
130  // Nope, couldn't find anything. This is an error, but just return the
131  // original filename.
132  if (_exists) {
133  _error_flag = true;
134  pandatoolbase_cat.error()
135  << "File does not exist: " << orig_filename << "\n";
136  } else if (pandatoolbase_cat.is_debug()) {
137  pandatoolbase_cat.debug()
138  << "File does not exist: " << orig_filename << "\n";
139  }
140  return orig_filename;
141 }
142 
143 /**
144  * Given a path to an existing filename, converts it as specified in the
145  * _path_store and or _path_directory properties to a form suitable for
146  * storing in an output file.
147  */
149 store_path(const Filename &orig_filename) {
150  if (orig_filename.empty()) {
151  return orig_filename;
152  }
153 
154  if (_path_directory.is_local()) {
155  _path_directory.make_absolute();
156  }
157  Filename filename = orig_filename;
158 
159  if (_copy_files) {
160  copy_this_file(filename);
161  }
162 
163  switch (_path_store) {
164  case PS_relative:
165  filename.make_absolute();
166  filename.make_relative_to(_path_directory);
167  break;
168 
169  case PS_absolute:
170  filename.make_absolute();
171  break;
172 
173  case PS_rel_abs:
174  filename.make_absolute();
175  filename.make_relative_to(_path_directory, false);
176  break;
177 
178  case PS_strip:
179  filename = filename.get_basename();
180  break;
181 
182  case PS_keep:
183  break;
184 
185  case PS_invalid:
186  break;
187  }
188 
189  return filename;
190 }
191 
192 /**
193  * Converts the input path into two different forms: A resolved path, and an
194  * output path. The resolved path is an absolute path if at all possible.
195  * The output path is in the form specified by the -ps path store option.
196  */
197 void PathReplace::
198 full_convert_path(const Filename &orig_filename,
199  const DSearchPath &additional_path,
200  Filename &resolved_path,
201  Filename &output_path) {
202  if (_path_directory.is_local()) {
203  _path_directory.make_absolute();
204  }
205 
206  Filename match;
207  bool got_match = false;
208 
210 
211  Entries::const_iterator ei;
212  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
213  const Entry &entry = (*ei);
214  Filename new_filename;
215  if (entry.try_match(orig_filename, new_filename)) {
216  // The prefix matches. Save the resulting filename for posterity.
217  got_match = true;
218  match = new_filename;
219 
220  if (new_filename.is_fully_qualified()) {
221  // If the resulting filename is fully qualified, it's a match if and
222  // only if it exists.
223  if (vfs->exists(new_filename)) {
224  resolved_path = new_filename;
225  goto calculate_output_path;
226  }
227 
228  } else {
229  // Otherwise, if it's a relative filename, attempt to look it up on
230  // the search path.
231  if (vfs->resolve_filename(new_filename, _path) ||
232  vfs->resolve_filename(new_filename, additional_path) ||
233  vfs->resolve_filename(new_filename, get_model_path())) {
234  // Found it!
235  resolved_path = new_filename;
236  goto calculate_output_path;
237  }
238  }
239 
240  // The prefix matched, but it didn't exist. Keep looking.
241  }
242  }
243 
244  // The file couldn't be found anywhere. Did we at least get any prefix
245  // match?
246  if (got_match) {
247  if (_exists) {
248  _error_flag = true;
249  pandatoolbase_cat.error()
250  << "File does not exist: " << match << "\n";
251  } else if (pandatoolbase_cat.is_debug()) {
252  pandatoolbase_cat.debug()
253  << "File does not exist: " << match << "\n";
254  }
255 
256  resolved_path = match;
257  goto calculate_output_path;
258  }
259 
260  if (!orig_filename.is_local()) {
261  // Ok, we didn't match any specified prefixes. If the file is an absolute
262  // pathname and we have _noabs set, that's an error.
263  if (_noabs) {
264  _error_flag = true;
265  pandatoolbase_cat.error()
266  << "Absolute pathname: " << orig_filename << "\n";
267  } else if (pandatoolbase_cat.is_debug()) {
268  pandatoolbase_cat.debug()
269  << "Absolute pathname: " << orig_filename << "\n";
270  }
271  }
272 
273  // Well, we still haven't found it; look it up on the search path as is.
274  {
275  Filename new_filename = orig_filename;
276  if (vfs->resolve_filename(new_filename, _path) ||
277  vfs->resolve_filename(new_filename, additional_path) ||
278  vfs->resolve_filename(new_filename, get_model_path())) {
279  // Found it!
280  match = orig_filename;
281  resolved_path = new_filename;
282  goto calculate_output_path;
283  }
284  }
285 
286  // Nope, couldn't find anything. This is an error, but just return the
287  // original filename.
288  if (_exists) {
289  _error_flag = true;
290  pandatoolbase_cat.error()
291  << "File does not exist: " << orig_filename << "\n";
292  } else if (pandatoolbase_cat.is_debug()) {
293  pandatoolbase_cat.debug()
294  << "File does not exist: " << orig_filename << "\n";
295  }
296  match = orig_filename;
297  resolved_path = orig_filename;
298 
299  // To calculate the output path, we need two inputs: the match, and the
300  // resolved path. Which one is used depends upon the path-store mode.
301  calculate_output_path:
302 
303  if (_copy_files) {
304  if (copy_this_file(resolved_path)) {
305  match = resolved_path;
306  }
307  }
308 
309  switch (_path_store) {
310  case PS_relative:
311  if (resolved_path.empty())
312  output_path = resolved_path;
313  else {
314  output_path = resolved_path;
315  output_path.make_absolute();
316  output_path.make_relative_to(_path_directory);
317  }
318  break;
319 
320  case PS_absolute:
321  if (resolved_path.empty())
322  output_path = resolved_path;
323  else {
324  output_path = resolved_path;
325  output_path.make_absolute();
326  }
327  break;
328 
329  case PS_rel_abs:
330  if (resolved_path.empty())
331  output_path = resolved_path;
332  else {
333  output_path = resolved_path;
334  output_path.make_absolute();
335  output_path.make_relative_to(_path_directory, false);
336  }
337  break;
338 
339  case PS_strip:
340  output_path = match.get_basename();
341  break;
342 
343  case PS_keep:
344  output_path = match;
345  break;
346 
347  case PS_invalid:
348  output_path = "";
349  break;
350  }
351 }
352 
353 /**
354  *
355  */
356 void PathReplace::
357 write(std::ostream &out, int indent_level) const {
358  Entries::const_iterator ei;
359  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
360  indent(out, indent_level)
361  << "-pr " << (*ei)._orig_prefix << "="
362  << (*ei)._replacement_prefix << "\n";
363  }
364  int num_directories = _path.get_num_directories();
365  for (int i = 0; i < num_directories; i++) {
366  indent(out, indent_level)
367  << "-pp " << _path.get_directory(i) << "\n";
368  }
369  indent(out, indent_level)
370  << "-ps " << _path_store << "\n";
371 
372  // The path directory is only relevant if _path_store is rel or rel_abs.
373  switch (_path_store) {
374  case PS_relative:
375  case PS_rel_abs:
376  indent(out, indent_level)
377  << "-pd " << _path_directory << "\n";
378 
379  default:
380  break;
381  }
382 
383  if (_copy_files) {
384  indent(out, indent_level)
385  << "-pc " << _copy_into_directory << "\n";
386  }
387 
388  if (_noabs) {
389  indent(out, indent_level)
390  << "-noabs\n";
391  }
392 }
393 
394 /**
395  * Copies the indicated file into the copy_into_directory, and adjusts
396  * filename to reference the new location. Returns true if the copy is made
397  * and the filename is changed, false otherwise.
398  */
399 bool PathReplace::
400 copy_this_file(Filename &filename) {
401  if (_copy_into_directory.is_local()) {
402  _copy_into_directory = Filename(_path_directory, _copy_into_directory);
403  }
404 
405  Copied::iterator ci = _orig_to_target.find(filename);
406  if (ci != _orig_to_target.end()) {
407  // This file has already been successfully copied, so we can quietly
408  // return its new target filename.
409  if (filename != (*ci).second) {
410  filename = (*ci).second;
411  return true;
412  }
413  return false;
414  }
415 
416  Filename target_filename(_copy_into_directory, filename.get_basename());
417  ci = _target_to_orig.find(target_filename);
418  if (ci != _target_to_orig.end()) {
419  if ((*ci).second != filename) {
420  _error_flag = true;
421  pandatoolbase_cat.error()
422  << "Filename conflict! Both " << (*ci).second << " and "
423  << filename << " map to " << target_filename << "\n";
424  }
425 
426  // Don't copy this one.
427  _orig_to_target[filename] = filename;
428  return false;
429  }
430 
431  _orig_to_target[filename] = target_filename;
432  _target_to_orig[target_filename] = filename;
433 
434  // Make the copy.
436  vfs->make_directory_full(_copy_into_directory);
437  if (!vfs->copy_file(filename, target_filename)) {
438  _error_flag = true;
439  pandatoolbase_cat.error()
440  << "Cannot copy file from " << filename << " to " << target_filename
441  << "\n";
442  _orig_to_target[filename] = filename;
443  return false;
444  }
445 
446  filename = target_filename;
447  return true;
448 }
449 
450 /**
451  *
452  */
453 PathReplace::Entry::
454 Entry(const std::string &orig_prefix, const std::string &replacement_prefix) :
455  _orig_prefix(orig_prefix),
456  _replacement_prefix(replacement_prefix)
457 {
458  // Eliminate trailing slashes; they're implicit.
459  if (_orig_prefix.length() > 1 &&
460  _orig_prefix[_orig_prefix.length() - 1] == '/') {
461  _orig_prefix = _orig_prefix.substr(0, _orig_prefix.length() - 1);
462  }
463  if (_replacement_prefix.length() > 1 &&
464  _replacement_prefix[_replacement_prefix.length() - 1] == '/') {
465  _replacement_prefix = _replacement_prefix.substr(0, _replacement_prefix.length() - 1);
466  }
467 
468  Filename filename(_orig_prefix);
469  _is_local = filename.is_local();
470 
471  vector_string components;
472  filename.extract_components(components);
473  vector_string::const_iterator ci;
474  for (ci = components.begin(); ci != components.end(); ++ci) {
475  _orig_components.push_back(Component(*ci));
476  }
477 }
478 
479 /**
480  * Considers whether the indicated filename matches this entry's prefix. If
481  * so, switches the prefix and stores the result in new_filename, and returns
482  * true; otherwise, returns false.
483  */
484 bool PathReplace::Entry::
485 try_match(const Filename &filename, Filename &new_filename) const {
486  if (_is_local != filename.is_local()) {
487  return false;
488  }
489  vector_string components;
490  filename.extract_components(components);
491  size_t mi = r_try_match(components, 0, 0);
492  if (mi == 0) {
493  // Sorry, no match.
494  return false;
495  }
496 
497  // We found a match. Construct the replacement string.
498  std::string result = _replacement_prefix;
499  while (mi < components.size()) {
500  if (!result.empty()) {
501  result += '/';
502  }
503  result += components[mi];
504  ++mi;
505  }
506  new_filename = result;
507  return true;
508 }
509 
510 /**
511  * The recursive implementation of try_match(). Actually, this is doubly-
512  * recursive, to implement the "**" feature.
513  *
514  * The return value is the number of the "components" vector that successfully
515  * matched against all of the orig_components. (It's a variable number
516  * because there might be one or more "**" entries.)
517  */
518 size_t PathReplace::Entry::
519 r_try_match(const vector_string &components, size_t oi, size_t ci) const {
520  if (oi >= _orig_components.size()) {
521  // If we ran out of user-supplied components, we're done.
522  return ci;
523  }
524  if (ci >= components.size()) {
525  // If we reached the end of the string, but we still have user-supplied
526  // components, we failed. (Arguably there should be a special case here
527  // for a user-supplied string that ends in "**", but I don't think the
528  // user ever wants to match the complete string.)
529  return 0;
530  }
531 
532  const Component &orig_component = _orig_components[oi];
533  if (orig_component._double_star) {
534  // If we have a double star, first consider the match if it were expanded
535  // as far as possible.
536  size_t mi = r_try_match(components, oi, ci + 1);
537  if (mi != 0) {
538  return mi;
539  }
540 
541  // Then try the match as if it there were no double star entry.
542  return r_try_match(components, oi + 1, ci);
543  }
544 
545  // We don't have a double star, it's just a one-for-one component entry.
546  // Does it match?
547  if (orig_component._orig_prefix.matches(components[ci])) {
548  // It does! Keep going.
549  return r_try_match(components, oi + 1, ci + 1);
550  }
551 
552  // It doesn't match, sorry.
553  return 0;
554 }
bool copy_file(const Filename &orig_filename, const Filename &new_filename)
Attempts to copy the contents of the indicated file to the indicated file.
Filename match_path(const Filename &orig_filename, const DSearchPath &additional_path=DSearchPath())
Looks for a match for the given filename among all the replacement patterns, and returns the first ma...
Definition: pathReplace.cxx:46
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
void full_convert_path(const Filename &orig_filename, const DSearchPath &additional_path, Filename &resolved_path, Filename &output_path)
Converts the input path into two different forms: A resolved path, and an output path.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
bool make_directory_full(const Filename &filename)
Attempts to create a directory within the file system.
bool is_fully_qualified() const
Returns true if the filename is fully qualified, e.g.
Definition: filename.I:562
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_num_directories
Returns the number of directories on the search list.
Definition: dSearchPath.h:76
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
void extract_components(vector_string &components) const
Extracts out the individual directory components of the path into a series of strings.
Definition: filename.cxx:872
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:549
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool make_relative_to(Filename directory, bool allow_backups=true)
Adjusts this filename, which must be a fully-specified pathname beginning with a slash, to make it a relative filename, relative to the fully- specified directory indicated (which must also begin with, and may or may not end with, a slash–a terminating slash is ignored).
Definition: filename.cxx:1640
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
void make_absolute()
Converts the filename to a fully-qualified pathname from the root (if it is a relative pathname)...
Definition: filename.cxx:968
get_directory
Returns the nth directory on the search list.
Definition: dSearchPath.h:76
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class stores a list of directories that can be searched, in order, to locate a particular file...
Definition: dSearchPath.h:28
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Filename store_path(const Filename &orig_filename)
Given a path to an existing filename, converts it as specified in the _path_store and or _path_direct...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.