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