Panda3D
|
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