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 */
45 Filename PathReplace::
46 match_path(const Filename &orig_filename,
47 const DSearchPath &additional_path) {
48 Filename match;
49 bool got_match = false;
50
51 VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
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 */
148 Filename PathReplace::
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
209 VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
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.
435 VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
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 }
DSearchPath
This class stores a list of directories that can be searched, in order, to locate a particular file.
Definition: dSearchPath.h:28
DSearchPath::get_num_directories
get_num_directories
Returns the number of directories on the search list.
Definition: dSearchPath.h:76
DSearchPath::get_directory
get_directory
Returns the nth directory on the search list.
Definition: dSearchPath.h:76
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Filename::is_fully_qualified
bool is_fully_qualified() const
Returns true if the filename is fully qualified, e.g.
Definition: filename.I:562
Filename::get_basename
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
Filename::extract_components
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
Filename::make_relative_to
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
Filename::is_local
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:549
Filename::make_absolute
void make_absolute()
Converts the filename to a fully-qualified pathname from the root (if it is a relative pathname),...
Definition: filename.cxx:968
PathReplace::store_path
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...
Definition: pathReplace.cxx:149
PathReplace::full_convert_path
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.
Definition: pathReplace.cxx:198
PathReplace::match_path
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
VirtualFileSystem
A hierarchy of directories and files that appears to be one continuous file system,...
Definition: virtualFileSystem.h:40
VirtualFileSystem::exists
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists.
Definition: virtualFileSystem.I:18
VirtualFileSystem::resolve_filename
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
Definition: virtualFileSystem.cxx:640
VirtualFileSystem::make_directory_full
bool make_directory_full(const Filename &filename)
Attempts to create a directory within the file system.
Definition: virtualFileSystem.cxx:483
VirtualFileSystem::copy_file
bool copy_file(const Filename &orig_filename, const Filename &new_filename)
Attempts to copy the contents of the indicated file to the indicated file.
Definition: virtualFileSystem.cxx:620
VirtualFileSystem::get_global_ptr
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
Definition: virtualFileSystem.cxx:741
config_pandatoolbase.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
config_putil.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
indent
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
indent.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pathReplace.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtualFileSystem.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.