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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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) {
485  _start_fullpath = cwd.to_os_specific();
486  }
487  return _start_fullpath;
488 }
489 
490 
491 /**
492  * Creates an invalid FilePath specification.
493  */
495 FilePath() :
496  _dir(nullptr)
497 {
498 }
499 
500 /**
501  * Creates a valid FilePath specification with the indicated directory and
502  * basename.
503  */
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  */
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  */
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  */
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  */
543 get_rel_from(const CVSSourceDirectory *other) const {
544  nassertr(_dir != nullptr, Filename());
545  return Filename(other->get_rel_to(_dir), _basename);
546 }
CVSSourceTree::FilePath::get_fullpath
Filename get_fullpath() const
Returns the full path to this file.
Definition: cvsSourceTree.cxx:533
ExecutionEnvironment::get_cwd
get_cwd
Returns the name of the current working directory.
Definition: executionEnvironment.h:61
CVSSourceTree::temp_chdir
static bool temp_chdir(const Filename &path)
Temporarily changes the current directory to the named path.
Definition: cvsSourceTree.cxx:210
pvector
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
string_utils.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
cvsSourceTree.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
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
CVSSourceDirectory::scan
bool scan(const Filename &directory, const std::string &key_filename)
Recursively scans the contents of the source directory.
Definition: cvsSourceDirectory.cxx:206
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
cvsSourceDirectory.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
filename.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
pnotify.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
CVSSourceTree::get_root_dirname
Filename get_root_dirname() const
Returns the local directory name of the root of the tree.
Definition: cvsSourceTree.cxx:189
CVSSourceDirectory
This represents one particular directory in the hierarchy of source directory files.
Definition: cvsSourceDirectory.h:35
CVSSourceDirectory::get_dirname
std::string get_dirname() const
Returns the local name of this particular directory.
Definition: cvsSourceDirectory.cxx:54
CVSSourceTree::set_root
void set_root(const Filename &root_path)
Sets the root of the source directory.
Definition: cvsSourceTree.cxx:60
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::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::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
executionEnvironment.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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::restore_cwd
static void restore_cwd()
Restores the current directory after changing it from temp_chdir().
Definition: cvsSourceTree.cxx:226
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::FilePath::FilePath
FilePath()
Creates an invalid FilePath specification.
Definition: cvsSourceTree.cxx:495
CVSSourceTree::get_root
CVSSourceDirectory * get_root() const
Returns the root directory of the hierarchy.
Definition: cvsSourceTree.cxx:82
Filename::get_basename
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
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::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_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
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
CVSSourceTree::scan
bool scan(const Filename &key_filename)
Scans the complete source directory starting at the indicated pathname.
Definition: cvsSourceTree.cxx:71