Panda3D

cvsSourceTree.cxx

00001 // Filename: cvsSourceTree.cxx
00002 // Created by:  drose (31Oct00)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "cvsSourceTree.h"
00016 #include "cvsSourceDirectory.h"
00017 
00018 #include "filename.h"
00019 #include "executionEnvironment.h"
00020 #include "pnotify.h"
00021 #include "string_utils.h"
00022 
00023 #include <algorithm>
00024 #include <ctype.h>
00025 #include <stdio.h> // for perror
00026 #include <errno.h>
00027 
00028 #ifdef WIN32_VC
00029 #include <direct.h>  // for chdir
00030 #endif
00031 
00032 bool CVSSourceTree::_got_start_fullpath = false;
00033 Filename CVSSourceTree::_start_fullpath;
00034 
00035 ////////////////////////////////////////////////////////////////////
00036 //     Function: CVSSourceTree::Constructor
00037 //       Access: Public
00038 //  Description:
00039 ////////////////////////////////////////////////////////////////////
00040 CVSSourceTree::
00041 CVSSourceTree() {
00042   _root = (CVSSourceDirectory *)NULL;
00043   _got_root_fullpath = false;
00044 }
00045 
00046 ////////////////////////////////////////////////////////////////////
00047 //     Function: CVSSourceTree::Destructor
00048 //       Access: Public
00049 //  Description:
00050 ////////////////////////////////////////////////////////////////////
00051 CVSSourceTree::
00052 ~CVSSourceTree() {
00053   if (_root != (CVSSourceDirectory *)NULL) {
00054     delete _root;
00055   }
00056 }
00057 
00058 ////////////////////////////////////////////////////////////////////
00059 //     Function: CVSSourceTree::set_root
00060 //       Access: Public
00061 //  Description: Sets the root of the source directory.  This must be
00062 //               called before scan(), and should not be called more
00063 //               than once.
00064 ////////////////////////////////////////////////////////////////////
00065 void CVSSourceTree::
00066 set_root(const Filename &root_path) {
00067   nassertv(_path.empty());
00068   _path = root_path;
00069 }
00070 
00071 ////////////////////////////////////////////////////////////////////
00072 //     Function: CVSSourceTree::scan
00073 //       Access: Public
00074 //  Description: Scans the complete source directory starting at the
00075 //               indicated pathname.  It is an error to call this more
00076 //               than once.  Returns true on success, false if there
00077 //               is an error.
00078 ////////////////////////////////////////////////////////////////////
00079 bool CVSSourceTree::
00080 scan(const Filename &key_filename) {
00081   nassertr(_root == (CVSSourceDirectory *)NULL, false);
00082   Filename root_fullpath = get_root_fullpath();
00083   _root = new CVSSourceDirectory(this, NULL, root_fullpath.get_basename());
00084   return _root->scan(_path, key_filename);
00085 }
00086 
00087 ////////////////////////////////////////////////////////////////////
00088 //     Function: CVSSourceTree::get_root
00089 //       Access: Public
00090 //  Description: Returns the root directory of the hierarchy.
00091 ////////////////////////////////////////////////////////////////////
00092 CVSSourceDirectory *CVSSourceTree::
00093 get_root() const {
00094   return _root;
00095 }
00096 
00097 ////////////////////////////////////////////////////////////////////
00098 //     Function: CVSSourceTree::find_directory
00099 //       Access: Public
00100 //  Description: Returns the source directory that corresponds to the
00101 //               given path, or NULL if there is no such directory in
00102 //               the source tree.
00103 ////////////////////////////////////////////////////////////////////
00104 CVSSourceDirectory *CVSSourceTree::
00105 find_directory(const Filename &path) {
00106   string root_fullpath = get_root_fullpath();
00107   string fullpath = get_actual_fullpath(path);
00108 
00109   // path is a subdirectory within the source hierarchy if and only if
00110   // root_fullpath is an initial prefix of fullpath.
00111   if (root_fullpath.length() > fullpath.length() ||
00112       cmp_nocase(fullpath.substr(0, root_fullpath.length()), root_fullpath) != 0) {
00113     // Nope!
00114     return (CVSSourceDirectory *)NULL;
00115   }
00116 
00117   // The relative name is the part of fullpath not in root_fullpath.
00118   Filename relpath = fullpath.substr(root_fullpath.length());
00119 
00120   return _root->find_relpath(relpath);
00121 }
00122 
00123 ////////////////////////////////////////////////////////////////////
00124 //     Function: CVSSourceTree::find_relpath
00125 //       Access: Public
00126 //  Description: Returns the source directory that corresponds to the
00127 //               given relative path from the root, or NULL if there
00128 //               is no match.  The relative path may or may not
00129 //               include the name of the root directory itself.
00130 ////////////////////////////////////////////////////////////////////
00131 CVSSourceDirectory *CVSSourceTree::
00132 find_relpath(const string &relpath) {
00133   CVSSourceDirectory *result = _root->find_relpath(relpath);
00134   if (result != (CVSSourceDirectory *)NULL) {
00135     return result;
00136   }
00137 
00138   // Check for the root dirname at the front of the path, and remove
00139   // it if it's there.
00140   size_t slash = relpath.find('/');
00141   Filename first = relpath.substr(0, slash);
00142   Filename rest;
00143   if (slash != string::npos) {
00144     rest = relpath.substr(slash + 1);
00145   }
00146 
00147   if (cmp_nocase(first, _root->get_dirname()) == 0) {
00148     return _root->find_relpath(rest);
00149   }
00150 
00151   return (CVSSourceDirectory *)NULL;
00152 }
00153 
00154 ////////////////////////////////////////////////////////////////////
00155 //     Function: CVSSourceTree::find_dirname
00156 //       Access: Public
00157 //  Description: Returns the source directory that corresponds to the
00158 //               given local directory name, or NULL if there
00159 //               is no match.
00160 ////////////////////////////////////////////////////////////////////
00161 CVSSourceDirectory *CVSSourceTree::
00162 find_dirname(const string &dirname) {
00163   return _root->find_dirname(dirname);
00164 }
00165 
00166 ////////////////////////////////////////////////////////////////////
00167 //     Function: CVSSourceTree::choose_directory
00168 //       Access: Public
00169 //  Description: Determines where an externally referenced model file
00170 //               of the indicated name should go.  It does this by
00171 //               looking for an existing model file of the same name;
00172 //               if a matching model is not found, or if multiple
00173 //               matching files are found, prompts the user for the
00174 //               directory, or uses suggested_dir.
00175 ////////////////////////////////////////////////////////////////////
00176 CVSSourceTree::FilePath CVSSourceTree::
00177 choose_directory(const string &basename, CVSSourceDirectory *suggested_dir,
00178                  bool force, bool interactive) {
00179   static FilePaths empty_paths;
00180 
00181   Basenames::const_iterator bi;
00182   bi = _basenames.find(downcase(basename));
00183   if (bi != _basenames.end()) {
00184     // The filename already exists somewhere.
00185     const FilePaths &paths = (*bi).second;
00186 
00187     return prompt_user(basename, suggested_dir, paths,
00188                        force, interactive);
00189   }
00190 
00191   // Now we have to prompt the user for a suitable place to put it.
00192   return prompt_user(basename, suggested_dir, empty_paths,
00193                      force, interactive);
00194 }
00195 
00196 ////////////////////////////////////////////////////////////////////
00197 //     Function: CVSSourceTree::get_root_fullpath
00198 //       Access: Public
00199 //  Description: Returns the full path from the root to the top of
00200 //               the source hierarchy.
00201 ////////////////////////////////////////////////////////////////////
00202 Filename CVSSourceTree::
00203 get_root_fullpath() {
00204   nassertr(!_path.empty(), Filename());
00205   if (!_got_root_fullpath) {
00206     _root_fullpath = get_actual_fullpath(_path);
00207     _got_root_fullpath = true;
00208   }
00209   return _root_fullpath;
00210 }
00211 
00212 ////////////////////////////////////////////////////////////////////
00213 //     Function: CVSSourceTree::get_root_dirname
00214 //       Access: Public
00215 //  Description: Returns the local directory name of the root of the
00216 //               tree.
00217 ////////////////////////////////////////////////////////////////////
00218 Filename CVSSourceTree::
00219 get_root_dirname() const {
00220   nassertr(_root != (CVSSourceDirectory *)NULL, Filename());
00221   return _root->get_dirname();
00222 }
00223 
00224 ////////////////////////////////////////////////////////////////////
00225 //     Function: CVSSourceTree::add_file
00226 //       Access: Public
00227 //  Description: Adds a new file to the set of known files.  This is
00228 //               normally called from CVSSourceDirectory::scan() and
00229 //               should not be called directly by the user.
00230 ////////////////////////////////////////////////////////////////////
00231 void CVSSourceTree::
00232 add_file(const string &basename, CVSSourceDirectory *dir) {
00233   FilePath file_path(dir, basename);
00234   _basenames[downcase(basename)].push_back(file_path);
00235 }
00236 
00237 ////////////////////////////////////////////////////////////////////
00238 //     Function: CVSSourceTree::temp_chdir
00239 //       Access: Public, Static
00240 //  Description: Temporarily changes the current directory to the
00241 //               named path.  Returns true on success, false on
00242 //               failure.  Call restore_cwd() to restore to the
00243 //               original directory later.
00244 ////////////////////////////////////////////////////////////////////
00245 bool CVSSourceTree::
00246 temp_chdir(const Filename &path) {
00247   // We have to call this first to guarantee that we have already
00248   // determined our starting directory.
00249   get_start_fullpath();
00250 
00251   string os_path = path.to_os_specific();
00252   if (chdir(os_path.c_str()) < 0) {
00253     return false;
00254   }
00255   return true;
00256 }
00257 
00258 ////////////////////////////////////////////////////////////////////
00259 //     Function: CVSSourceTree::restore_cwd
00260 //       Access: Public, Static
00261 //  Description: Restores the current directory after changing it from
00262 //               temp_chdir().
00263 ////////////////////////////////////////////////////////////////////
00264 void CVSSourceTree::
00265 restore_cwd() {
00266   Filename start_fullpath = get_start_fullpath();
00267   string os_path = start_fullpath.to_os_specific();
00268 
00269   if (chdir(os_path.c_str()) < 0) {
00270     // Hey!  We can't get back to the directory we started from!
00271     perror(os_path.c_str());
00272     nout << "Can't continue, aborting.\n";
00273     exit(1);
00274   }
00275 }
00276 
00277 
00278 ////////////////////////////////////////////////////////////////////
00279 //     Function: CVSSourceTree::prompt_user
00280 //       Access: Private
00281 //  Description: Prompts the user, if necessary, to choose a directory
00282 //               to import the given file into.
00283 ////////////////////////////////////////////////////////////////////
00284 CVSSourceTree::FilePath CVSSourceTree::
00285 prompt_user(const string &basename, CVSSourceDirectory *suggested_dir,
00286             const CVSSourceTree::FilePaths &paths,
00287             bool force, bool interactive) {
00288   if (paths.size() == 1) {
00289     // The file already exists in exactly one place.
00290     if (!interactive) {
00291       return paths[0];
00292     }
00293     FilePath result = ask_existing(basename, paths[0]);
00294     if (result.is_valid()) {
00295       return result;
00296     }
00297 
00298   } else if (paths.size() > 1) {
00299     // The file already exists in multiple places.
00300     if (force && !interactive) {
00301       return paths[0];
00302     }
00303     FilePath result = ask_existing(basename, paths, suggested_dir);
00304     if (result.is_valid()) {
00305       return result;
00306     }
00307   }
00308 
00309   // The file does not already exist, or the user declined to replace
00310   // an existing file.
00311   if (force && !interactive) {
00312     return FilePath(suggested_dir, basename);
00313   }
00314 
00315   // Is the file already in the suggested directory?  If not, prompt
00316   // the user to put it there.
00317   bool found_dir = false;
00318   FilePaths::const_iterator pi;
00319   for (pi = paths.begin(); pi != paths.end(); ++pi) {
00320     if ((*pi)._dir == suggested_dir) {
00321       found_dir = true;
00322       break;
00323     }
00324   }
00325 
00326   if (!found_dir) {
00327     FilePath result = ask_new(basename, suggested_dir);
00328     if (result.is_valid()) {
00329       return result;
00330     }
00331   }
00332 
00333   // Ask the user where the damn thing should go.
00334   return ask_any(basename, paths);
00335 }
00336 
00337 ////////////////////////////////////////////////////////////////////
00338 //     Function: CVSSourceTree::ask_existing
00339 //       Access: Private
00340 //  Description: Asks the user if he wants to replace an existing
00341 //               file.
00342 ////////////////////////////////////////////////////////////////////
00343 CVSSourceTree::FilePath CVSSourceTree::
00344 ask_existing(const string &basename, const CVSSourceTree::FilePath &path) {
00345   while (true) {
00346     nout << basename << " found in tree at "
00347          << path.get_path() << ".\n";
00348     string result = prompt("Overwrite this file (y/n)? ");
00349     nassertr(!result.empty(), FilePath());
00350     if (result.size() == 1) {
00351       if (tolower(result[0]) == 'y') {
00352         return path;
00353       } else if (tolower(result[0]) == 'n') {
00354         return FilePath();
00355       }
00356     }
00357 
00358     nout << "*** Invalid response: " << result << "\n\n";
00359   }
00360 }
00361 
00362 ////////////////////////////////////////////////////////////////////
00363 //     Function: CVSSourceTree::ask_existing
00364 //       Access: Private
00365 //  Description: Asks the user which of several existing files he
00366 //               wants to replace.
00367 ////////////////////////////////////////////////////////////////////
00368 CVSSourceTree::FilePath CVSSourceTree::
00369 ask_existing(const string &basename, const CVSSourceTree::FilePaths &paths,
00370              CVSSourceDirectory *suggested_dir) {
00371   while (true) {
00372     nout << basename << " found in tree at more than one place:\n";
00373 
00374     bool any_suggested = false;
00375     for (int i = 0; i < (int)paths.size(); i++) {
00376       nout << "  " << (i + 1) << ". "
00377            << paths[i].get_path() << "\n";
00378       if (paths[i]._dir == suggested_dir) {
00379         any_suggested = true;
00380       }
00381     }
00382 
00383     int next_option = paths.size() + 1;
00384     int suggested_option = -1;
00385 
00386     if (!any_suggested) {
00387       // If it wasn't already in the suggested directory, offer to put
00388       // it there.
00389       suggested_option = next_option;
00390       next_option++;
00391       nout << "\n" << suggested_option
00392            << ". create "
00393            << Filename(suggested_dir->get_path(), basename)
00394            << "\n";
00395     }
00396 
00397     int other_option = next_option;
00398     nout << other_option << ". Other\n";
00399 
00400     string result = prompt("Choose an option: ");
00401     nassertr(!result.empty(), FilePath());
00402     const char *nptr = result.c_str();
00403     char *endptr;
00404     int option = strtol(nptr, &endptr, 10);
00405     if (*endptr == '\0') {
00406       if (option >= 1 && option <= (int)paths.size()) {
00407         return paths[option - 1];
00408 
00409       } else if (option == suggested_option) {
00410         return FilePath(suggested_dir, basename);
00411 
00412       } else if (option == other_option) {
00413         return FilePath();
00414       }
00415     }
00416 
00417     nout << "*** Invalid response: " << result << "\n\n";
00418   }
00419 }
00420 
00421 ////////////////////////////////////////////////////////////////////
00422 //     Function: CVSSourceTree::ask_new
00423 //       Access: Private
00424 //  Description: Asks the user if he wants to create a new file.
00425 ////////////////////////////////////////////////////////////////////
00426 CVSSourceTree::FilePath CVSSourceTree::
00427 ask_new(const string &basename, CVSSourceDirectory *dir) {
00428   while (true) {
00429     nout << basename << " will be created in "
00430          << dir->get_path() << ".\n";
00431     string result = prompt("Create this file (y/n)? ");
00432     nassertr(!result.empty(), FilePath());
00433     if (result.size() == 1) {
00434       if (tolower(result[0]) == 'y') {
00435         return FilePath(dir, basename);
00436       } else if (tolower(result[0]) == 'n') {
00437         return FilePath();
00438       }
00439     }
00440 
00441     nout << "*** Invalid response: " << result << "\n\n";
00442   }
00443 }
00444 
00445 ////////////////////////////////////////////////////////////////////
00446 //     Function: CVSSourceTree::ask_any
00447 //       Access: Private
00448 //  Description: Asks the user to type in the name of the directory in
00449 //               which to store the file.
00450 ////////////////////////////////////////////////////////////////////
00451 CVSSourceTree::FilePath CVSSourceTree::
00452 ask_any(const string &basename,
00453         const CVSSourceTree::FilePaths &paths) {
00454   while (true) {
00455     string result =
00456       prompt("Enter the name of the directory to copy " + basename + " to: ");
00457     nassertr(!result.empty(), FilePath());
00458 
00459     // The user might enter a fully-qualified path to the directory,
00460     // or a relative path from the root (with or without the root's
00461     // dirname), or the dirname of the particular directory.
00462     CVSSourceDirectory *dir = find_directory(result);
00463     if (dir == (CVSSourceDirectory *)NULL) {
00464       dir = find_relpath(result);
00465     }
00466     if (dir == (CVSSourceDirectory *)NULL) {
00467       dir = find_dirname(result);
00468     }
00469 
00470     if (dir != (CVSSourceDirectory *)NULL) {
00471       // If the file is already in this directory, we must preserve
00472       // its existing case.
00473       FilePaths::const_iterator pi;
00474       for (pi = paths.begin(); pi != paths.end(); ++pi) {
00475         if ((*pi)._dir == dir) {
00476           return (*pi);
00477         }
00478       }
00479 
00480       // Otherwise, since we're creating a new file, keep the original
00481       // case.
00482       return FilePath(dir, basename);
00483     }
00484 
00485     nout << "*** Not a valid directory name: " << result << "\n\n";
00486   }
00487 }
00488 
00489 ////////////////////////////////////////////////////////////////////
00490 //     Function: CVSSourceTree::prompt
00491 //       Access: Private
00492 //  Description: Issues a prompt to the user and waits for a typed
00493 //               response.  Returns the response (which will not be
00494 //               empty).
00495 ////////////////////////////////////////////////////////////////////
00496 string CVSSourceTree::
00497 prompt(const string &message) {
00498   nout << flush;
00499   while (true) {
00500     cerr << message << flush;
00501     string response;
00502     getline(cin, response);
00503 
00504     // Remove leading and trailing whitespace.
00505     size_t p = 0;
00506     while (p < response.length() && isspace(response[p])) {
00507       p++;
00508     }
00509 
00510     size_t q = response.length();
00511     while (q > p && isspace(response[q - 1])) {
00512       q--;
00513     }
00514 
00515     if (q > p) {
00516       return response.substr(p, q - p);
00517     }
00518   }
00519 }
00520 
00521 ////////////////////////////////////////////////////////////////////
00522 //     Function: CVSSourceTree::get_actual_fullpath
00523 //       Access: Private, Static
00524 //  Description: Determines the actual full path from the root to the
00525 //               named directory.
00526 ////////////////////////////////////////////////////////////////////
00527 Filename CVSSourceTree::
00528 get_actual_fullpath(const Filename &path) {
00529   Filename canon = path;
00530   canon.make_canonical();
00531   return canon;
00532 }
00533 
00534 
00535 ////////////////////////////////////////////////////////////////////
00536 //     Function: CVSSourceTree::get_start_fullpath
00537 //       Access: Private, Static
00538 //  Description: Returns the full path from the root to the directory
00539 //               in which the user started the program.
00540 ////////////////////////////////////////////////////////////////////
00541 Filename CVSSourceTree::
00542 get_start_fullpath() {
00543   if (!_got_start_fullpath) {
00544     Filename cwd = ExecutionEnvironment::get_cwd();
00545     _start_fullpath = cwd.to_os_specific();
00546   }
00547   return _start_fullpath;
00548 }
00549 
00550 
00551 ////////////////////////////////////////////////////////////////////
00552 //     Function: CVSSourceTree::FilePath::Constructor
00553 //       Access: Public
00554 //  Description: Creates an invalid FilePath specification.
00555 ////////////////////////////////////////////////////////////////////
00556 CVSSourceTree::FilePath::
00557 FilePath() :
00558   _dir(NULL)
00559 {
00560 }
00561 
00562 ////////////////////////////////////////////////////////////////////
00563 //     Function: CVSSourceTree::FilePath::Constructor
00564 //       Access: Public
00565 //  Description: Creates a valid FilePath specification with the
00566 //               indicated directory and basename.
00567 ////////////////////////////////////////////////////////////////////
00568 CVSSourceTree::FilePath::
00569 FilePath(CVSSourceDirectory *dir, const string &basename) :
00570   _dir(dir),
00571   _basename(basename)
00572 {
00573 }
00574 
00575 ////////////////////////////////////////////////////////////////////
00576 //     Function: CVSSourceTree::FilePath::is_valid
00577 //       Access: Public
00578 //  Description: Returns true if this FilePath represents a valid
00579 //               file, or false if it represents an error return.
00580 ////////////////////////////////////////////////////////////////////
00581 bool CVSSourceTree::FilePath::
00582 is_valid() const {
00583   return (_dir != (CVSSourceDirectory *)NULL);
00584 }
00585 
00586 ////////////////////////////////////////////////////////////////////
00587 //     Function: CVSSourceTree::FilePath::get_path
00588 //       Access: Public
00589 //  Description: Returns the relative path to this file from the root
00590 //               of the source tree.
00591 ////////////////////////////////////////////////////////////////////
00592 Filename CVSSourceTree::FilePath::
00593 get_path() const {
00594   nassertr(_dir != (CVSSourceDirectory *)NULL, Filename());
00595   return Filename(_dir->get_path(), _basename);
00596 }
00597 
00598 ////////////////////////////////////////////////////////////////////
00599 //     Function: CVSSourceTree::FilePath::get_fullpath
00600 //       Access: Public
00601 //  Description: Returns the full path to this file.
00602 ////////////////////////////////////////////////////////////////////
00603 Filename CVSSourceTree::FilePath::
00604 get_fullpath() const {
00605   nassertr(_dir != (CVSSourceDirectory *)NULL, Filename());
00606   return Filename(_dir->get_fullpath(), _basename);
00607 }
00608 
00609 ////////////////////////////////////////////////////////////////////
00610 //     Function: CVSSourceTree::FilePath::get_rel_from
00611 //       Access: Public
00612 //  Description: Returns the relative path to this file as seen from
00613 //               the indicated source directory.
00614 ////////////////////////////////////////////////////////////////////
00615 Filename CVSSourceTree::FilePath::
00616 get_rel_from(const CVSSourceDirectory *other) const {
00617   nassertr(_dir != (CVSSourceDirectory *)NULL, Filename());
00618   return Filename(other->get_rel_to(_dir), _basename);
00619 }
00620 
 All Classes Functions Variables Enumerations