// Filename: cvsSourceTree.cxx // Created by: drose (31Oct00) // //////////////////////////////////////////////////////////////////// // // PANDA 3D SOFTWARE // Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved // // All use of this software is subject to the terms of the Panda 3d // Software license. You should have received a copy of this license // along with this source code; you will also find a current copy of // the license at http://etc.cmu.edu/panda3d/docs/license/ . // // To contact the maintainers of this program write to // panda3d-general@lists.sourceforge.net . // //////////////////////////////////////////////////////////////////// #include "cvsSourceTree.h" #include "cvsSourceDirectory.h" #include "filename.h" #include "executionEnvironment.h" #include "pnotify.h" #include "string_utils.h" #include #include #include // for perror #include #ifdef WIN32_VC #include // for chdir #endif bool CVSSourceTree::_got_start_fullpath = false; Filename CVSSourceTree::_start_fullpath; //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// CVSSourceTree:: CVSSourceTree() { _root = (CVSSourceDirectory *)NULL; _got_root_fullpath = false; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::Destructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// CVSSourceTree:: ~CVSSourceTree() { if (_root != (CVSSourceDirectory *)NULL) { delete _root; } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::set_root // Access: Public // Description: Sets the root of the source directory. This must be // called before scan(), and should not be called more // than once. //////////////////////////////////////////////////////////////////// void CVSSourceTree:: set_root(const Filename &root_path) { nassertv(_path.empty()); _path = root_path; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::scan // Access: Public // Description: Scans the complete source directory starting at the // indicated pathname. It is an error to call this more // than once. Returns true on success, false if there // is an error. //////////////////////////////////////////////////////////////////// bool CVSSourceTree:: scan(const Filename &key_filename) { nassertr(_root == (CVSSourceDirectory *)NULL, false); Filename root_fullpath = get_root_fullpath(); _root = new CVSSourceDirectory(this, NULL, root_fullpath.get_basename()); return _root->scan(_path, key_filename); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::get_root // Access: Public // Description: Returns the root directory of the hierarchy. //////////////////////////////////////////////////////////////////// CVSSourceDirectory *CVSSourceTree:: get_root() const { return _root; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::find_directory // Access: Public // Description: Returns the source directory that corresponds to the // given path, or NULL if there is no such directory in // the source tree. //////////////////////////////////////////////////////////////////// CVSSourceDirectory *CVSSourceTree:: find_directory(const Filename &path) { string root_fullpath = get_root_fullpath(); string fullpath = get_actual_fullpath(path); // path is a subdirectory within the source hierarchy if and only if // root_fullpath is an initial prefix of fullpath. if (root_fullpath.length() > fullpath.length() || cmp_nocase(fullpath.substr(0, root_fullpath.length()), root_fullpath) != 0) { // Nope! return (CVSSourceDirectory *)NULL; } // The relative name is the part of fullpath not in root_fullpath. Filename relpath = fullpath.substr(root_fullpath.length()); return _root->find_relpath(relpath); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::find_relpath // Access: Public // Description: Returns the source directory that corresponds to the // given relative path from the root, or NULL if there // is no match. The relative path may or may not // include the name of the root directory itself. //////////////////////////////////////////////////////////////////// CVSSourceDirectory *CVSSourceTree:: find_relpath(const string &relpath) { CVSSourceDirectory *result = _root->find_relpath(relpath); if (result != (CVSSourceDirectory *)NULL) { return result; } // Check for the root dirname at the front of the path, and remove // it if it's there. size_t slash = relpath.find('/'); Filename first = relpath.substr(0, slash); Filename rest; if (slash != string::npos) { rest = relpath.substr(slash + 1); } if (cmp_nocase(first, _root->get_dirname()) == 0) { return _root->find_relpath(rest); } return (CVSSourceDirectory *)NULL; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::find_dirname // Access: Public // Description: Returns the source directory that corresponds to the // given local directory name, or NULL if there // is no match. //////////////////////////////////////////////////////////////////// CVSSourceDirectory *CVSSourceTree:: find_dirname(const string &dirname) { return _root->find_dirname(dirname); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::choose_directory // Access: Public // Description: Determines where an externally referenced model file // of the indicated name should go. It does this by // looking for an existing model file of the same name; // if a matching model is not found, or if multiple // matching files are found, prompts the user for the // directory, or uses suggested_dir. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath CVSSourceTree:: choose_directory(const string &basename, CVSSourceDirectory *suggested_dir, bool force, bool interactive) { static FilePaths empty_paths; Basenames::const_iterator bi; bi = _basenames.find(downcase(basename)); if (bi != _basenames.end()) { // The filename already exists somewhere. const FilePaths &paths = (*bi).second; return prompt_user(basename, suggested_dir, paths, force, interactive); } // Now we have to prompt the user for a suitable place to put it. return prompt_user(basename, suggested_dir, empty_paths, force, interactive); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::get_root_fullpath // Access: Public // Description: Returns the full path from the root to the top of // the source hierarchy. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree:: get_root_fullpath() { nassertr(!_path.empty(), Filename()); if (!_got_root_fullpath) { _root_fullpath = get_actual_fullpath(_path); _got_root_fullpath = true; } return _root_fullpath; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::get_root_dirname // Access: Public // Description: Returns the local directory name of the root of the // tree. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree:: get_root_dirname() const { nassertr(_root != (CVSSourceDirectory *)NULL, Filename()); return _root->get_dirname(); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::add_file // Access: Public // Description: Adds a new file to the set of known files. This is // normally called from CVSSourceDirectory::scan() and // should not be called directly by the user. //////////////////////////////////////////////////////////////////// void CVSSourceTree:: add_file(const string &basename, CVSSourceDirectory *dir) { FilePath file_path(dir, basename); _basenames[downcase(basename)].push_back(file_path); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::temp_chdir // Access: Public, Static // Description: Temporarily changes the current directory to the // named path. Returns true on success, false on // failure. Call restore_cwd() to restore to the // original directory later. //////////////////////////////////////////////////////////////////// bool CVSSourceTree:: temp_chdir(const Filename &path) { // We have to call this first to guarantee that we have already // determined our starting directory. get_start_fullpath(); string os_path = path.to_os_specific(); if (chdir(os_path.c_str()) < 0) { return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::restore_cwd // Access: Public, Static // Description: Restores the current directory after changing it from // temp_chdir(). //////////////////////////////////////////////////////////////////// void CVSSourceTree:: restore_cwd() { Filename start_fullpath = get_start_fullpath(); string os_path = start_fullpath.to_os_specific(); if (chdir(os_path.c_str()) < 0) { // Hey! We can't get back to the directory we started from! perror(os_path.c_str()); nout << "Can't continue, aborting.\n"; exit(1); } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::prompt_user // Access: Private // Description: Prompts the user, if necessary, to choose a directory // to import the given file into. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath CVSSourceTree:: prompt_user(const string &basename, CVSSourceDirectory *suggested_dir, const CVSSourceTree::FilePaths &paths, bool force, bool interactive) { if (paths.size() == 1) { // The file already exists in exactly one place. if (!interactive) { return paths[0]; } FilePath result = ask_existing(basename, paths[0]); if (result.is_valid()) { return result; } } else if (paths.size() > 1) { // The file already exists in multiple places. if (force && !interactive) { return paths[0]; } FilePath result = ask_existing(basename, paths, suggested_dir); if (result.is_valid()) { return result; } } // The file does not already exist, or the user declined to replace // an existing file. if (force && !interactive) { return FilePath(suggested_dir, basename); } // Is the file already in the suggested directory? If not, prompt // the user to put it there. bool found_dir = false; FilePaths::const_iterator pi; for (pi = paths.begin(); pi != paths.end(); ++pi) { if ((*pi)._dir == suggested_dir) { found_dir = true; break; } } if (!found_dir) { FilePath result = ask_new(basename, suggested_dir); if (result.is_valid()) { return result; } } // Ask the user where the damn thing should go. return ask_any(basename, paths); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::ask_existing // Access: Private // Description: Asks the user if he wants to replace an existing // file. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath CVSSourceTree:: ask_existing(const string &basename, const CVSSourceTree::FilePath &path) { while (true) { nout << basename << " found in tree at " << path.get_path() << ".\n"; string result = prompt("Overwrite this file (y/n)? "); nassertr(!result.empty(), FilePath()); if (result.size() == 1) { if (tolower(result[0]) == 'y') { return path; } else if (tolower(result[0]) == 'n') { return FilePath(); } } nout << "*** Invalid response: " << result << "\n\n"; } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::ask_existing // Access: Private // Description: Asks the user which of several existing files he // wants to replace. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath CVSSourceTree:: ask_existing(const string &basename, const CVSSourceTree::FilePaths &paths, CVSSourceDirectory *suggested_dir) { while (true) { nout << basename << " found in tree at more than one place:\n"; bool any_suggested = false; for (int i = 0; i < (int)paths.size(); i++) { nout << " " << (i + 1) << ". " << paths[i].get_path() << "\n"; if (paths[i]._dir == suggested_dir) { any_suggested = true; } } int next_option = paths.size() + 1; int suggested_option = -1; if (!any_suggested) { // If it wasn't already in the suggested directory, offer to put // it there. suggested_option = next_option; next_option++; nout << "\n" << suggested_option << ". create " << Filename(suggested_dir->get_path(), basename) << "\n"; } int other_option = next_option; nout << other_option << ". Other\n"; string result = prompt("Choose an option: "); nassertr(!result.empty(), FilePath()); const char *nptr = result.c_str(); char *endptr; int option = strtol(nptr, &endptr, 10); if (*endptr == '\0') { if (option >= 1 && option <= (int)paths.size()) { return paths[option - 1]; } else if (option == suggested_option) { return FilePath(suggested_dir, basename); } else if (option == other_option) { return FilePath(); } } nout << "*** Invalid response: " << result << "\n\n"; } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::ask_new // Access: Private // Description: Asks the user if he wants to create a new file. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath CVSSourceTree:: ask_new(const string &basename, CVSSourceDirectory *dir) { while (true) { nout << basename << " will be created in " << dir->get_path() << ".\n"; string result = prompt("Create this file (y/n)? "); nassertr(!result.empty(), FilePath()); if (result.size() == 1) { if (tolower(result[0]) == 'y') { return FilePath(dir, basename); } else if (tolower(result[0]) == 'n') { return FilePath(); } } nout << "*** Invalid response: " << result << "\n\n"; } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::ask_any // Access: Private // Description: Asks the user to type in the name of the directory in // which to store the file. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath CVSSourceTree:: ask_any(const string &basename, const CVSSourceTree::FilePaths &paths) { while (true) { string result = prompt("Enter the name of the directory to copy " + basename + " to: "); nassertr(!result.empty(), FilePath()); // The user might enter a fully-qualified path to the directory, // or a relative path from the root (with or without the root's // dirname), or the dirname of the particular directory. CVSSourceDirectory *dir = find_directory(result); if (dir == (CVSSourceDirectory *)NULL) { dir = find_relpath(result); } if (dir == (CVSSourceDirectory *)NULL) { dir = find_dirname(result); } if (dir != (CVSSourceDirectory *)NULL) { // If the file is already in this directory, we must preserve // its existing case. FilePaths::const_iterator pi; for (pi = paths.begin(); pi != paths.end(); ++pi) { if ((*pi)._dir == dir) { return (*pi); } } // Otherwise, since we're creating a new file, keep the original // case. return FilePath(dir, basename); } nout << "*** Not a valid directory name: " << result << "\n\n"; } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::prompt // Access: Private // Description: Issues a prompt to the user and waits for a typed // response. Returns the response (which will not be // empty). //////////////////////////////////////////////////////////////////// string CVSSourceTree:: prompt(const string &message) { nout << flush; while (true) { cerr << message << flush; string response; getline(cin, response); // Remove leading and trailing whitespace. size_t p = 0; while (p < response.length() && isspace(response[p])) { p++; } size_t q = response.length(); while (q > p && isspace(response[q - 1])) { q--; } if (q > p) { return response.substr(p, q - p); } } } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::get_actual_fullpath // Access: Private, Static // Description: Determines the actual full path from the root to the // named directory. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree:: get_actual_fullpath(const Filename &path) { Filename canon = path; canon.make_canonical(); return canon; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::get_start_fullpath // Access: Private, Static // Description: Returns the full path from the root to the directory // in which the user started the program. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree:: get_start_fullpath() { if (!_got_start_fullpath) { Filename cwd = ExecutionEnvironment::get_cwd(); _start_fullpath = cwd.to_os_specific(); } return _start_fullpath; } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::FilePath::Constructor // Access: Public // Description: Creates an invalid FilePath specification. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath:: FilePath() : _dir(NULL) { } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::FilePath::Constructor // Access: Public // Description: Creates a valid FilePath specification with the // indicated directory and basename. //////////////////////////////////////////////////////////////////// CVSSourceTree::FilePath:: FilePath(CVSSourceDirectory *dir, const string &basename) : _dir(dir), _basename(basename) { } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::FilePath::is_valid // Access: Public // Description: Returns true if this FilePath represents a valid // file, or false if it represents an error return. //////////////////////////////////////////////////////////////////// bool CVSSourceTree::FilePath:: is_valid() const { return (_dir != (CVSSourceDirectory *)NULL); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::FilePath::get_path // Access: Public // Description: Returns the relative path to this file from the root // of the source tree. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree::FilePath:: get_path() const { nassertr(_dir != (CVSSourceDirectory *)NULL, Filename()); return Filename(_dir->get_path(), _basename); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::FilePath::get_fullpath // Access: Public // Description: Returns the full path to this file. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree::FilePath:: get_fullpath() const { nassertr(_dir != (CVSSourceDirectory *)NULL, Filename()); return Filename(_dir->get_fullpath(), _basename); } //////////////////////////////////////////////////////////////////// // Function: CVSSourceTree::FilePath::get_rel_from // Access: Public // Description: Returns the relative path to this file as seen from // the indicated source directory. //////////////////////////////////////////////////////////////////// Filename CVSSourceTree::FilePath:: get_rel_from(const CVSSourceDirectory *other) const { nassertr(_dir != (CVSSourceDirectory *)NULL, Filename()); return Filename(other->get_rel_to(_dir), _basename); }