Panda3D

cvsSourceTree.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 cvsSourceTree.cxx
10 * @author drose
11 * @date 2000-10-31
12 */
13
14 #include "cvsSourceTree.h"
15 #include "cvsSourceDirectory.h"
16
17 #include "filename.h"
18 #include "executionEnvironment.h"
19 #include "pnotify.h"
20 #include "string_utils.h"
21
22 #include <algorithm>
23 #include <ctype.h>
24 #include <stdio.h> // for perror
25 #include <errno.h>
26
27 #ifdef WIN32_VC
28 #include <direct.h> // for chdir
29 #endif
30
31 using std::string;
32
33 bool CVSSourceTree::_got_start_fullpath = false;
34 Filename CVSSourceTree::_start_fullpath;
35
36 /**
37 *
38 */
39 CVSSourceTree::
40 CVSSourceTree() {
41 _root = nullptr;
42 _got_root_fullpath = false;
43 }
44
45 /**
46 *
47 */
48 CVSSourceTree::
49 ~CVSSourceTree() {
50 if (_root != nullptr) {
51 delete _root;
52 }
53 }
54
55 /**
56 * Sets the root of the source directory. This must be called before scan(),
57 * and should not be called more than once.
58 */
59 void CVSSourceTree::
60 set_root(const Filename &root_path) {
61 nassertv(_path.empty());
62 _path = root_path;
63 }
64
65 /**
66 * Scans the complete source directory starting at the indicated pathname. It
67 * is an error to call this more than once. Returns true on success, false if
68 * there is an error.
69 */
70 bool CVSSourceTree::
71 scan(const Filename &key_filename) {
72 nassertr(_root == nullptr, false);
73 Filename root_fullpath = get_root_fullpath();
74 _root = new CVSSourceDirectory(this, nullptr, root_fullpath.get_basename());
75 return _root->scan(_path, key_filename);
76 }
77
78 /**
79 * Returns the root directory of the hierarchy.
80 */
81 CVSSourceDirectory *CVSSourceTree::
82 get_root() const {
83 return _root;
84 }
85
86 /**
87 * Returns the source directory that corresponds to the given path, or NULL if
88 * there is no such directory in the source tree.
89 */
90 CVSSourceDirectory *CVSSourceTree::
91 find_directory(const Filename &path) {
92 string root_fullpath = get_root_fullpath();
93 string fullpath = get_actual_fullpath(path);
94
95 // path is a subdirectory within the source hierarchy if and only if
96 // root_fullpath is an initial prefix of fullpath.
97 if (root_fullpath.length() > fullpath.length() ||
98 cmp_nocase(fullpath.substr(0, root_fullpath.length()), root_fullpath) != 0) {
99 // Nope!
100 return nullptr;
101 }
102
103 // The relative name is the part of fullpath not in root_fullpath.
104 Filename relpath = fullpath.substr(root_fullpath.length());
105
106 return _root->find_relpath(relpath);
107 }
108
109 /**
110 * Returns the source directory that corresponds to the given relative path
111 * from the root, or NULL if there is no match. The relative path may or may
112 * not include the name of the root directory itself.
113 */
114 CVSSourceDirectory *CVSSourceTree::
115 find_relpath(const string &relpath) {
116 CVSSourceDirectory *result = _root->find_relpath(relpath);
117 if (result != nullptr) {
118 return result;
119 }
120
121 // Check for the root dirname at the front of the path, and remove it if
122 // it's there.
123 size_t slash = relpath.find('/');
124 Filename first = relpath.substr(0, slash);
125 Filename rest;
126 if (slash != string::npos) {
127 rest = relpath.substr(slash + 1);
128 }
129
130 if (cmp_nocase(first, _root->get_dirname()) == 0) {
131 return _root->find_relpath(rest);
132 }
133
134 return nullptr;
135 }
136
137 /**
138 * Returns the source directory that corresponds to the given local directory
139 * name, or NULL if there is no match.
140 */
141 CVSSourceDirectory *CVSSourceTree::
142 find_dirname(const string &dirname) {
143 return _root->find_dirname(dirname);
144 }
145
146 /**
147 * Determines where an externally referenced model file of the indicated name
148 * should go. It does this by looking for an existing model file of the same
149 * name; if a matching model is not found, or if multiple matching files are
150 * found, prompts the user for the directory, or uses suggested_dir.
151 */
152 CVSSourceTree::FilePath CVSSourceTree::
153 choose_directory(const string &basename, CVSSourceDirectory *suggested_dir,
154 bool force, bool interactive) {
155 static FilePaths empty_paths;
156
157 Basenames::const_iterator bi;
158 bi = _basenames.find(downcase(basename));
159 if (bi != _basenames.end()) {
160 // The filename already exists somewhere.
161 const FilePaths &paths = (*bi).second;
162
163 return prompt_user(basename, suggested_dir, paths,
164 force, interactive);
165 }
166
167 // Now we have to prompt the user for a suitable place to put it.
168 return prompt_user(basename, suggested_dir, empty_paths,
169 force, interactive);
170 }
171
172 /**
173 * Returns the full path from the root to the top of the source hierarchy.
174 */
175 Filename CVSSourceTree::
176 get_root_fullpath() {
177 nassertr(!_path.empty(), Filename());
178 if (!_got_root_fullpath) {
179 _root_fullpath = get_actual_fullpath(_path);
180 _got_root_fullpath = true;
181 }
182 return _root_fullpath;
183 }
184
185 /**
186 * Returns the local directory name of the root of the tree.
187 */
188 Filename CVSSourceTree::
189 get_root_dirname() const {
190 nassertr(_root != nullptr, Filename());
191 return _root->get_dirname();
192 }
193
194 /**
195 * Adds a new file to the set of known files. This is normally called from
196 * CVSSourceDirectory::scan() and should not be called directly by the user.
197 */
198 void CVSSourceTree::
199 add_file(const string &basename, CVSSourceDirectory *dir) {
200 FilePath file_path(dir, basename);
201 _basenames[downcase(basename)].push_back(file_path);
202 }
203
204 /**
205 * Temporarily changes the current directory to the named path. Returns true
206 * on success, false on failure. Call restore_cwd() to restore to the
207 * original directory later.
208 */
209 bool CVSSourceTree::
210 temp_chdir(const Filename &path) {
211 // We have to call this first to guarantee that we have already determined
212 // our starting directory.
213 get_start_fullpath();
214
215 string os_path = path.to_os_specific();
216 if (chdir(os_path.c_str()) < 0) {
217 return false;
218 }
219 return true;
220 }
221
222 /**
223 * Restores the current directory after changing it from temp_chdir().
224 */
225 void CVSSourceTree::
226 restore_cwd() {
227 Filename start_fullpath = get_start_fullpath();
228 string os_path = start_fullpath.to_os_specific();
229
230 if (chdir(os_path.c_str()) < 0) {
231 // Hey! We can't get back to the directory we started from!
232 perror(os_path.c_str());
233 nout << "Can't continue, aborting.\n";
234 exit(1);
235 }
236 }
237
238
239 /**
240 * Prompts the user, if necessary, to choose a directory to import the given
241 * file into.
242 */
243 CVSSourceTree::FilePath CVSSourceTree::
244 prompt_user(const string &basename, CVSSourceDirectory *suggested_dir,
245 const CVSSourceTree::FilePaths &paths,
246 bool force, bool interactive) {
247 if (paths.size() == 1) {
248 // The file already exists in exactly one place.
249 if (!interactive) {
250 return paths[0];
251 }
252 FilePath result = ask_existing(basename, paths[0]);
253 if (result.is_valid()) {
254 return result;
255 }
256
257 } else if (paths.size() > 1) {
258 // The file already exists in multiple places.
259 if (force && !interactive) {
260 return paths[0];
261 }
262 FilePath result = ask_existing(basename, paths, suggested_dir);
263 if (result.is_valid()) {
264 return result;
265 }
266 }
267
268 // The file does not already exist, or the user declined to replace an
269 // existing file.
270 if (force && !interactive) {
271 return FilePath(suggested_dir, basename);
272 }
273
274 // Is the file already in the suggested directory? If not, prompt the user
275 // to put it there.
276 bool found_dir = false;
277 FilePaths::const_iterator pi;
278 for (pi = paths.begin(); pi != paths.end(); ++pi) {
279 if ((*pi)._dir == suggested_dir) {
280 found_dir = true;
281 break;
282 }
283 }
284
285 if (!found_dir) {
286 FilePath result = ask_new(basename, suggested_dir);
287 if (result.is_valid()) {
288 return result;
289 }
290 }
291
292 // Ask the user where the damn thing should go.
293 return ask_any(basename, paths);
294 }
295
296 /**
297 * Asks the user if he wants to replace an existing file.
298 */
299 CVSSourceTree::FilePath CVSSourceTree::
300 ask_existing(const string &basename, const CVSSourceTree::FilePath &path) {
301 while (true) {
302 nout << basename << " found in tree at "
303 << path.get_path() << ".\n";
304 string result = prompt("Overwrite this file (y/n)? ");
305 nassertr(!result.empty(), FilePath());
306 if (result.size() == 1) {
307 if (tolower(result[0]) == 'y') {
308 return path;
309 } else if (tolower(result[0]) == 'n') {
310 return FilePath();
311 }
312 }
313
314 nout << "*** Invalid response: " << result << "\n\n";
315 }
316 }
317
318 /**
319 * Asks the user which of several existing files he wants to replace.
320 */
321 CVSSourceTree::FilePath CVSSourceTree::
322 ask_existing(const string &basename, const CVSSourceTree::FilePaths &paths,
323 CVSSourceDirectory *suggested_dir) {
324 while (true) {
325 nout << basename << " found in tree at more than one place:\n";
326
327 bool any_suggested = false;
328 for (int i = 0; i < (int)paths.size(); i++) {
329 nout << " " << (i + 1) << ". "
330 << paths[i].get_path() << "\n";
331 if (paths[i]._dir == suggested_dir) {
332 any_suggested = true;
333 }
334 }
335
336 int next_option = paths.size() + 1;
337 int suggested_option = -1;
338
339 if (!any_suggested) {
340 // If it wasn't already in the suggested directory, offer to put it
341 // there.
342 suggested_option = next_option;
343 next_option++;
344 nout << "\n" << suggested_option
345 << ". create "
346 << Filename(suggested_dir->get_path(), basename)
347 << "\n";
348 }
349
350 int other_option = next_option;
351 nout << other_option << ". Other\n";
352
353 string result = prompt("Choose an option: ");
354 nassertr(!result.empty(), FilePath());
355 const char *nptr = result.c_str();
356 char *endptr;
357 int option = strtol(nptr, &endptr, 10);
358 if (*endptr == '\0') {
359 if (option >= 1 && option <= (int)paths.size()) {
360 return paths[option - 1];
361
362 } else if (option == suggested_option) {
363 return FilePath(suggested_dir, basename);
364
365 } else if (option == other_option) {
366 return FilePath();
367 }
368 }
369
370 nout << "*** Invalid response: " << result << "\n\n";
371 }
372 }
373
374 /**
375 * Asks the user if he wants to create a new file.
376 */
377 CVSSourceTree::FilePath CVSSourceTree::
378 ask_new(const string &basename, CVSSourceDirectory *dir) {
379 while (true) {
380 nout << basename << " will be created in "
381 << dir->get_path() << ".\n";
382 string result = prompt("Create this file (y/n)? ");
383 nassertr(!result.empty(), FilePath());
384 if (result.size() == 1) {
385 if (tolower(result[0]) == 'y') {
386 return FilePath(dir, basename);
387 } else if (tolower(result[0]) == 'n') {
388 return FilePath();
389 }
390 }
391
392 nout << "*** Invalid response: " << result << "\n\n";
393 }
394 }
395
396 /**
397 * Asks the user to type in the name of the directory in which to store the
398 * file.
399 */
400 CVSSourceTree::FilePath CVSSourceTree::
401 ask_any(const string &basename,
402 const CVSSourceTree::FilePaths &paths) {
403 while (true) {
404 string result =
405 prompt("Enter the name of the directory to copy " + basename + " to: ");
406 nassertr(!result.empty(), FilePath());
407
408 // The user might enter a fully-qualified path to the directory, or a
409 // relative path from the root (with or without the root's dirname), or
410 // the dirname of the particular directory.
411 CVSSourceDirectory *dir = find_directory(result);
412 if (dir == nullptr) {
413 dir = find_relpath(result);
414 }
415 if (dir == nullptr) {
416 dir = find_dirname(result);
417 }
418
419 if (dir != nullptr) {
420 // If the file is already in this directory, we must preserve its
421 // existing case.
422 FilePaths::const_iterator pi;
423 for (pi = paths.begin(); pi != paths.end(); ++pi) {
424 if ((*pi)._dir == dir) {
425 return (*pi);
426 }
427 }
428
429 // Otherwise, since we're creating a new file, keep the original case.
430 return FilePath(dir, basename);
431 }
432
433 nout << "*** Not a valid directory name: " << result << "\n\n";
434 }
435 }
436
437 /**
438 * Issues a prompt to the user and waits for a typed response. Returns the
439 * response (which will not be empty).
440 */
441 string CVSSourceTree::
442 prompt(const string &message) {
443 nout << std::flush;
444 while (true) {
445 std::cerr << message << std::flush;
446 std::string response;
447 std::getline(std::cin, response);
448
449 // Remove leading and trailing whitespace.
450 size_t p = 0;
451 while (p < response.length() && isspace(response[p])) {
452 p++;
453 }
454
455 size_t q = response.length();
456 while (q > p && isspace(response[q - 1])) {
457 q--;
458 }
459
460 if (q > p) {
461 return response.substr(p, q - p);
462 }
463 }
464 }
465
466 /**
467 * Determines the actual full path from the root to the named directory.
468 */
469 Filename CVSSourceTree::
470 get_actual_fullpath(const Filename &path) {
471 Filename canon = path;
472 canon.make_canonical();
473 return canon;
474 }
475
476
477 /**
478 * Returns the full path from the root to the directory in which the user
479 * started the program.
480 */
481 Filename CVSSourceTree::
482 get_start_fullpath() {
483 if (!_got_start_fullpath) {
484 Filename cwd = ExecutionEnvironment::get_cwd();
485 _start_fullpath = cwd.to_os_specific();
486 }
487 return _start_fullpath;
488 }
489
490
491 /**
492 * Creates an invalid FilePath specification.
493 */
494 CVSSourceTree::FilePath::
495 FilePath() :
496 _dir(nullptr)
497 {
498 }
499
500 /**
501 * Creates a valid FilePath specification with the indicated directory and
502 * basename.
503 */
504 CVSSourceTree::FilePath::
505 FilePath(CVSSourceDirectory *dir, const string &basename) :
506 _dir(dir),
507 _basename(basename)
508 {
509 }
510
511 /**
512 * Returns true if this FilePath represents a valid file, or false if it
513 * represents an error return.
514 */
515 bool CVSSourceTree::FilePath::
516 is_valid() const {
517 return (_dir != nullptr);
518 }
519
520 /**
521 * Returns the relative path to this file from the root of the source tree.
522 */
523 Filename CVSSourceTree::FilePath::
524 get_path() const {
525 nassertr(_dir != nullptr, Filename());
526 return Filename(_dir->get_path(), _basename);
527 }
528
529 /**
530 * Returns the full path to this file.
531 */
532 Filename CVSSourceTree::FilePath::
533 get_fullpath() const {
534 nassertr(_dir != nullptr, Filename());
535 return Filename(_dir->get_fullpath(), _basename);
536 }
537
538 /**
539 * Returns the relative path to this file as seen from the indicated source
540 * directory.
541 */
542 Filename CVSSourceTree::FilePath::
543 get_rel_from(const CVSSourceDirectory *other) const {
544 nassertr(_dir != nullptr, Filename());
545 return Filename(other->get_rel_to(_dir), _basename);
546 }
CVSSourceDirectory
This represents one particular directory in the hierarchy of source directory files.
Definition: cvsSourceDirectory.h:35
CVSSourceDirectory::scan
bool scan(const Filename &directory, const std::string &key_filename)
Recursively scans the contents of the source directory.
Definition: cvsSourceDirectory.cxx:206
CVSSourceDirectory::find_dirname
CVSSourceDirectory * find_dirname(const std::string &dirname)
Returns the source directory that corresponds to the given local directory name, or NULL if there is ...
Definition: cvsSourceDirectory.cxx:183
CVSSourceDirectory::get_rel_to
Filename get_rel_to(const CVSSourceDirectory *other) const
Returns the relative path to the other directory from this one.
Definition: cvsSourceDirectory.cxx:86
CVSSourceDirectory::get_dirname
std::string get_dirname() const
Returns the local name of this particular directory.
Definition: cvsSourceDirectory.cxx:54
CVSSourceDirectory::find_relpath
CVSSourceDirectory * find_relpath(const std::string &relpath)
Returns the source directory that corresponds to the given relative path from this directory,...
Definition: cvsSourceDirectory.cxx:143
CVSSourceDirectory::get_path
Filename get_path() const
Returns the relative pathname to this particular directory, as seen from the root of the tree.
Definition: cvsSourceDirectory.cxx:74
CVSSourceTree::FilePath
Definition: cvsSourceTree.h:52
CVSSourceTree::FilePath::get_path
Filename get_path() const
Returns the relative path to this file from the root of the source tree.
Definition: cvsSourceTree.cxx:524
CVSSourceTree::FilePath::FilePath
FilePath()
Creates an invalid FilePath specification.
Definition: cvsSourceTree.cxx:495
CVSSourceTree::FilePath::get_rel_from
Filename get_rel_from(const CVSSourceDirectory *other) const
Returns the relative path to this file as seen from the indicated source directory.
Definition: cvsSourceTree.cxx:543
CVSSourceTree::FilePath::is_valid
bool is_valid() const
Returns true if this FilePath represents a valid file, or false if it represents an error return.
Definition: cvsSourceTree.cxx:516
CVSSourceTree::FilePath::get_fullpath
Filename get_fullpath() const
Returns the full path to this file.
Definition: cvsSourceTree.cxx:533
CVSSourceTree::get_root
CVSSourceDirectory * get_root() const
Returns the root directory of the hierarchy.
Definition: cvsSourceTree.cxx:82
CVSSourceTree::get_root_fullpath
Filename get_root_fullpath()
Returns the full path from the root to the top of the source hierarchy.
Definition: cvsSourceTree.cxx:176
CVSSourceTree::add_file
void add_file(const std::string &basename, CVSSourceDirectory *dir)
Adds a new file to the set of known files.
Definition: cvsSourceTree.cxx:199
CVSSourceTree::set_root
void set_root(const Filename &root_path)
Sets the root of the source directory.
Definition: cvsSourceTree.cxx:60
CVSSourceTree::choose_directory
FilePath choose_directory(const std::string &basename, CVSSourceDirectory *suggested_dir, bool force, bool interactive)
Determines where an externally referenced model file of the indicated name should go.
Definition: cvsSourceTree.cxx:153
CVSSourceTree::get_root_dirname
Filename get_root_dirname() const
Returns the local directory name of the root of the tree.
Definition: cvsSourceTree.cxx:189
CVSSourceTree::find_relpath
CVSSourceDirectory * find_relpath(const std::string &relpath)
Returns the source directory that corresponds to the given relative path from the root,...
Definition: cvsSourceTree.cxx:115
CVSSourceTree::temp_chdir
static bool temp_chdir(const Filename &path)
Temporarily changes the current directory to the named path.
Definition: cvsSourceTree.cxx:210
CVSSourceTree::find_directory
CVSSourceDirectory * find_directory(const Filename &path)
Returns the source directory that corresponds to the given path, or NULL if there is no such director...
Definition: cvsSourceTree.cxx:91
CVSSourceTree::restore_cwd
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
Definition: cvsSourceTree.cxx:226
CVSSourceTree::scan
bool scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
Definition: cvsSourceTree.cxx:71
CVSSourceTree::find_dirname
CVSSourceDirectory * find_dirname(const std::string &dirname)
Returns the source directory that corresponds to the given local directory name, or NULL if there is ...
Definition: cvsSourceTree.cxx:142
ExecutionEnvironment::get_cwd
get_cwd
Returns the name of the current working directory.
Definition: executionEnvironment.h:61
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Filename::get_basename
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
Filename::to_os_specific
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1123
Filename::make_canonical
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1011
pvector
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
cvsSourceDirectory.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
cvsSourceTree.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
executionEnvironment.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
filename.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
pnotify.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
downcase
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
Definition: string_utils.cxx:71
string_utils.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.