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,...
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,...
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.