Panda3D
Loading...
Searching...
No Matches
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"
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
31using std::string;
32
33bool CVSSourceTree::_got_start_fullpath = false;
34Filename CVSSourceTree::_start_fullpath;
35
36/**
37 *
38 */
39CVSSourceTree::
40CVSSourceTree() {
41 _root = nullptr;
42 _got_root_fullpath = false;
43}
44
45/**
46 *
47 */
48CVSSourceTree::
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 */
60set_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 */
71scan(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 */
82get_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 */
91find_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 */
115find_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 */
142find_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 */
153choose_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 */
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 */
189get_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 */
199add_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 */
210temp_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 */
226restore_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 */
243CVSSourceTree::FilePath CVSSourceTree::
244prompt_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 */
299CVSSourceTree::FilePath CVSSourceTree::
300ask_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 */
321CVSSourceTree::FilePath CVSSourceTree::
322ask_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 */
377CVSSourceTree::FilePath CVSSourceTree::
378ask_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 */
400CVSSourceTree::FilePath CVSSourceTree::
401ask_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 */
441string CVSSourceTree::
442prompt(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 */
469Filename CVSSourceTree::
470get_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 */
481Filename CVSSourceTree::
482get_start_fullpath() {
483 if (!_got_start_fullpath) {
485 _start_fullpath = cwd.to_os_specific();
486 }
487 return _start_fullpath;
488}
489
490
491/**
492 * Creates an invalid FilePath specification.
493 */
495FilePath() :
496 _dir(nullptr)
497{
498}
499
500/**
501 * Creates a valid FilePath specification with the indicated directory and
502 * basename.
503 */
505FilePath(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 */
516is_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 */
524get_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 */
533get_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 */
543get_rel_from(const CVSSourceDirectory *other) const {
544 nassertr(_dir != nullptr, Filename());
545 return Filename(other->get_rel_to(_dir), _basename);
546}
This represents one particular directory in the hierarchy of source directory files.
bool scan(const Filename &directory, const std::string &key_filename)
Recursively scans the contents of the source directory.
CVSSourceDirectory * find_dirname(const std::string &dirname)
Returns the source directory that corresponds to the given local directory name, or NULL if there is ...
Filename get_rel_to(const CVSSourceDirectory *other) const
Returns the relative path to the other directory from this one.
std::string get_dirname() const
Returns the local name of this particular directory.
CVSSourceDirectory * find_relpath(const std::string &relpath)
Returns the source directory that corresponds to the given relative path from this directory,...
Filename get_path() const
Returns the relative pathname to this particular directory, as seen from the root of the tree.
Filename get_path() const
Returns the relative path to this file from the root of the source tree.
FilePath()
Creates an invalid FilePath specification.
Filename get_rel_from(const CVSSourceDirectory *other) const
Returns the relative path to this file as seen from the indicated source directory.
bool is_valid() const
Returns true if this FilePath represents a valid file, or false if it represents an error return.
Filename get_fullpath() const
Returns the full path to this file.
CVSSourceDirectory * get_root() const
Returns the root directory of the hierarchy.
Filename get_root_fullpath()
Returns the full path from the root to the top of the source hierarchy.
void add_file(const std::string &basename, CVSSourceDirectory *dir)
Adds a new file to the set of known files.
void set_root(const Filename &root_path)
Sets the root of the source 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.
Filename get_root_dirname() const
Returns the local directory name of the root of the tree.
CVSSourceDirectory * find_relpath(const std::string &relpath)
Returns the source directory that corresponds to the given relative path from the root,...
static bool temp_chdir(const Filename &path)
Temporarily changes the current directory to the named path.
CVSSourceDirectory * find_directory(const Filename &path)
Returns the source directory that corresponds to the given path, or NULL if there is no such director...
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
bool scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
CVSSourceDirectory * find_dirname(const std::string &dirname)
Returns the source directory that corresponds to the given local directory name, or NULL if there is ...
get_cwd
Returns the name of the current working directory.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string get_basename() const
Returns the basename part of the filename.
Definition filename.I:367
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.