From 527b16a099f0d988c6e2e2aa5061ba9040a90a9b Mon Sep 17 00:00:00 2001 From: David Rose Date: Wed, 1 Nov 2000 18:36:39 +0000 Subject: [PATCH] *** empty log message *** --- pandatool/src/cvscopy/Sources.pp | 28 + pandatool/src/cvscopy/cvsCopy.cxx | 278 ++++++++++ pandatool/src/cvscopy/cvsCopy.h | 67 +++ pandatool/src/cvscopy/cvsSourceDirectory.cxx | 270 +++++++++ pandatool/src/cvscopy/cvsSourceDirectory.h | 52 ++ pandatool/src/cvscopy/cvsSourceTree.cxx | 543 +++++++++++++++++++ pandatool/src/cvscopy/cvsSourceTree.h | 81 +++ pandatool/src/cvscopy/testCopy.cxx | 94 ++++ pandatool/src/cvscopy/testCopy.h | 29 + pandatool/src/progbase/programBase.cxx | 36 +- pandatool/src/progbase/programBase.h | 3 +- 11 files changed, 1473 insertions(+), 8 deletions(-) create mode 100644 pandatool/src/cvscopy/Sources.pp create mode 100644 pandatool/src/cvscopy/cvsCopy.cxx create mode 100644 pandatool/src/cvscopy/cvsCopy.h create mode 100644 pandatool/src/cvscopy/cvsSourceDirectory.cxx create mode 100644 pandatool/src/cvscopy/cvsSourceDirectory.h create mode 100644 pandatool/src/cvscopy/cvsSourceTree.cxx create mode 100644 pandatool/src/cvscopy/cvsSourceTree.h create mode 100644 pandatool/src/cvscopy/testCopy.cxx create mode 100644 pandatool/src/cvscopy/testCopy.h diff --git a/pandatool/src/cvscopy/Sources.pp b/pandatool/src/cvscopy/Sources.pp new file mode 100644 index 0000000000..a942bd3c38 --- /dev/null +++ b/pandatool/src/cvscopy/Sources.pp @@ -0,0 +1,28 @@ +#begin lib_target + #define TARGET cvscopy + #define LOCAL_LIBS \ + progbase pandatoolbase + + #define OTHER_LIBS \ + dconfig:c dtool:m + + #define SOURCES \ + cvsCopy.cxx cvsCopy.h cvsSourceDirectory.cxx cvsSourceDirectory.h \ + cvsSourceTree.cxx cvsSourceTree.h + + #define INSTALL_HEADERS \ + cvsCopy.h + +#end lib_target + +#begin test_bin_target + #define TARGET testcopy + #define LOCAL_LIBS cvscopy + + #define OTHER_LIBS \ + dconfig:c dtool:m pystub + + #define SOURCES \ + testCopy.cxx testCopy.h + +#end test_bin_target diff --git a/pandatool/src/cvscopy/cvsCopy.cxx b/pandatool/src/cvscopy/cvsCopy.cxx new file mode 100644 index 0000000000..b129a2459e --- /dev/null +++ b/pandatool/src/cvscopy/cvsCopy.cxx @@ -0,0 +1,278 @@ +// Filename: cvsCopy.cxx +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "cvsCopy.h" +#include "cvsSourceDirectory.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +CVSCopy:: +CVSCopy() { + _model_dirname = "."; + _key_filename = "Sources.pp"; + _cvs_binary = "cvs"; + _model_dir = (CVSSourceDirectory *)NULL; + _map_dir = (CVSSourceDirectory *)NULL; + + clear_runlines(); + add_runline("[opts] file [file ... ]"); + + add_option + ("f", "", 80, + "Force copy to happen without any input from the user. If a file " + "with the same name exists anywhere in the source hierarchy, it will " + "be overwritten without prompting; if a file does not yet exist, it " + "will be created in the directory named by -d or by -m, as appropriate.", + &CVSCopy::dispatch_none, &_force); + + add_option + ("i", "", 80, + "The opposite of -f, this will prompt the user before each action. " + "The default is only to prompt the user when an action is ambiguous.", + &CVSCopy::dispatch_none, &_interactive); + + add_option + ("d", "dirname", 80, + "Copy model files that are not already present somewhere in the tree " + "to the indicated directory. The default is the current directory.", + &CVSCopy::dispatch_filename, &_got_model_dirname, &_model_dirname); + + add_option + ("m", "dirname", 80, + "Copy texture map files to the indicated directory. The default " + "is src/maps from the root directory.", + &CVSCopy::dispatch_filename, &_got_map_dirname, &_map_dirname); + + add_option + ("root", "dirname", 80, + "Specify the root of the CVS source hierarchy. The default is to " + "use the ppremake convention of locating the directory containing " + "Package.pp.", + &CVSCopy::dispatch_filename, &_got_root_dirname, &_root_dirname); + + add_option + ("key", "filename", 80, + "Specify the name of the file that must exist in each directory for " + "it to be considered part of the CVS source hierarchy. The default " + "is the ppremake convention, \"Sources.pp\". Other likely candidates " + "are \"CVS\" to search the entire CVS hierarchy, or \".\" to include " + "all subdirectories.", + &CVSCopy::dispatch_filename, NULL, &_key_filename); + + add_option + ("nc", "", 80, + "Do not attempt to add newly-created files to CVS. The default " + "is to add them.", + &CVSCopy::dispatch_none, &_no_cvs); + + add_option + ("cvs", "cvs_binary", 80, + "Specify how to run the cvs program for adding newly-created files. " + "The default is simply \"cvs\".", + &CVSCopy::dispatch_string, NULL, &_cvs_binary); +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::import +// Access: Public +// Description: Checks for the given filename somewhere in the +// directory hierarchy, and chooses a place to import +// it. Copies the file by calling copy_file(). +// +// Type is an integer number that is defined by the +// derivated class; CVSCopy simply passes it unchanged +// to copy_file(). It presumably gives the class a hint +// as to how the file should be copied. Suggested_dir +// is the suggested directory in which to copy the file, +// if it does not already exist elsewhere. +// +// On success, returns the CVSSourceDirectory it was +// actually copied to. On failure, returns NULL. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSCopy:: +import(const Filename &source, int type, CVSSourceDirectory *suggested_dir) { + CopiedFiles::const_iterator ci; + ci = _copied_files.find(source); + if (ci != _copied_files.end()) { + // We have already copied this file. + return (*ci).second; + } + + if (!source.exists()) { + cerr << "Source filename " << source << " does not exist!\n"; + return (CVSSourceDirectory *)NULL; + } + + string basename = source.get_basename(); + + CVSSourceDirectory *dir = + _tree.choose_directory(basename, suggested_dir, _force, _interactive); + nassertr(dir != (CVSSourceDirectory *)NULL, dir); + + Filename dest = dir->get_fullpath() + "/" + basename; + + _copied_files[source] = dir; + + bool new_file = !dest.exists(); + if (!copy_file(source, dest, dir, type, new_file)) { + return (CVSSourceDirectory *)NULL; + } + if (new_file) { + create_file(dest); + } + + return dir; +} + + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::handle_args +// Access: Protected, Virtual +// Description: Does something with the additional arguments on the +// command line (after all the -options have been +// parsed). Returns true if the arguments are good, +// false otherwise. +//////////////////////////////////////////////////////////////////// +bool CVSCopy:: +handle_args(Args &args) { + if (args.empty()) { + nout << "You must specify the file(s) to copy from on the command line.\n"; + return false; + } + + _source_files = args; + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::post_command_line +// Access: Protected, Virtual +// Description: This is called after the command line has been +// completely processed, and it gives the program a +// chance to do some last-minute processing and +// validation of the options and arguments. It should +// return true if everything is fine, false if there is +// an error. +//////////////////////////////////////////////////////////////////// +bool CVSCopy:: +post_command_line() { + if (!scan_hierarchy()) { + return false; + } + + _model_dir = _tree.find_directory(_model_dirname); + if (_model_dir == (CVSSourceDirectory *)NULL) { + if (_got_model_dirname) { + nout << "Warning: model directory " << _model_dirname + << " is not within the source hierarchy.\n"; + } + } + + if (_got_map_dirname) { + _map_dir = _tree.find_directory(_map_dirname); + + if (_map_dir == (CVSSourceDirectory *)NULL) { + nout << "Warning: map directory " << _map_dirname + << " is not within the source hierarchy.\n"; + } + + } else { + _map_dir = _tree.find_relpath("src/maps"); + + if (_map_dir == (CVSSourceDirectory *)NULL) { + nout << "Warning: no directory " << _tree.get_root_dirname() + << "/src/maps.\n"; + _map_dir = _model_dir; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::create_file +// Access: Protected +// Description: Invokes CVS to add the indicated filename to the +// repository, if the user so requested. Returns true +// if successful, false if there is an error. +//////////////////////////////////////////////////////////////////// +bool CVSCopy:: +create_file(const Filename &filename) { + if (_no_cvs) { + return true; + } + + if (!CVSSourceTree::temp_chdir(filename.get_dirname())) { + nout << "Invalid directory: " << filename.get_dirname() << "\n"; + return false; + } + + string command = _cvs_binary + " add " + filename.get_basename(); + nout << command << "\n"; + int result = system(command.c_str()); + + CVSSourceTree::restore_cwd(); + + if (result != 0) { + nout << "Failure invoking cvs.\n"; + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::scan_hierarchy +// Access: Private +// Description: Starts the scan of the source hierarchy. This +// identifies all of the files in the source hierarchy +// we're to copy these into, so we can guess where +// referenced files should be placed. Returns true if +// everything is ok, false if there is an error. +//////////////////////////////////////////////////////////////////// +bool CVSCopy:: +scan_hierarchy() { + if (!_got_root_dirname) { + // If we didn't get a root directory name, find the directory + // above this one that contains the file "Package.pp". + if (!scan_for_root(_model_dirname)) { + return false; + } + } + + _tree.set_root(_root_dirname); + nout << "Root is " << _tree.get_root_fullpath() << "\n"; + + return _tree.scan(_key_filename); +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSCopy::scan_for_root +// Access: Private +// Description: Searches for the root of the source directory by +// looking for the parent directory that contains +// "Package.pp". Returns true on success, false on +// failure. +//////////////////////////////////////////////////////////////////// +bool CVSCopy:: +scan_for_root(const string &dirname) { + Filename sources = dirname + "/Sources.pp"; + if (!sources.exists()) { + nout << "Couldn't find " << sources << " in source directory.\n"; + return false; + } + Filename package = dirname + "/Package.pp"; + if (package.exists()) { + // Here's the root! + _root_dirname = dirname; + return true; + } + + return scan_for_root(dirname + "/.."); +} diff --git a/pandatool/src/cvscopy/cvsCopy.h b/pandatool/src/cvscopy/cvsCopy.h new file mode 100644 index 0000000000..f00ee8f211 --- /dev/null +++ b/pandatool/src/cvscopy/cvsCopy.h @@ -0,0 +1,67 @@ +// Filename: cvsCopy.h +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef CVSCOPY_H +#define CVSCOPY_H + +#include + +#include "cvsSourceTree.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : CVSCopy +// Description : This is the base class for a family of programs that +// copy files, typically model files like .flt files and +// their associated textures, into a CVS-controlled +// source tree. +//////////////////////////////////////////////////////////////////// +class CVSCopy : public ProgramBase { +public: + CVSCopy(); + + CVSSourceDirectory * + import(const Filename &source, int type, CVSSourceDirectory *suggested_dir); + +protected: + virtual bool handle_args(Args &args); + virtual bool post_command_line(); + + virtual bool copy_file(Filename source, Filename dest, + CVSSourceDirectory *dest_dir, + int type, bool new_file)=0; + bool create_file(const Filename &filename); + +private: + bool scan_hierarchy(); + bool scan_for_root(const string &dirname); + +protected: + bool _force; + bool _interactive; + bool _got_model_dirname; + Filename _model_dirname; + bool _got_map_dirname; + Filename _map_dirname; + bool _got_root_dirname; + Filename _root_dirname; + Filename _key_filename; + bool _no_cvs; + string _cvs_binary; + + typedef vector_string SourceFiles; + SourceFiles _source_files; + + CVSSourceTree _tree; + CVSSourceDirectory *_model_dir; + CVSSourceDirectory *_map_dir; + + typedef map CopiedFiles; + CopiedFiles _copied_files; +}; + +#endif diff --git a/pandatool/src/cvscopy/cvsSourceDirectory.cxx b/pandatool/src/cvscopy/cvsSourceDirectory.cxx new file mode 100644 index 0000000000..b5c834921a --- /dev/null +++ b/pandatool/src/cvscopy/cvsSourceDirectory.cxx @@ -0,0 +1,270 @@ +// Filename: cvsSourceDirectory.cxx +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "cvsSourceDirectory.h" +#include "cvsSourceTree.h" + +#include + +#ifdef WIN32_VC +// Windows uses a different API for scanning for files in a directory. +#define WINDOWS_LEAN_AND_MEAN +#include + +#else +#include +#include +#include +#endif + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory:: +CVSSourceDirectory(CVSSourceTree *tree, CVSSourceDirectory *parent, + const string &dirname) : + _tree(tree), + _parent(parent), + _dirname(dirname) +{ + if (_parent == (CVSSourceDirectory *)NULL) { + _depth = 0; + } else { + _depth = _parent->_depth + 1; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory:: +~CVSSourceDirectory() { + Children::iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + delete (*ci); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::get_dirname +// Access: Public +// Description: Returns the local name of this particular directory. +//////////////////////////////////////////////////////////////////// +string CVSSourceDirectory:: +get_dirname() const { + return _dirname; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::get_fullpath +// Access: Public +// Description: Returns the full pathname to this particular +// directory. +//////////////////////////////////////////////////////////////////// +string CVSSourceDirectory:: +get_fullpath() const { + if (_parent == (CVSSourceDirectory *)NULL) { + return _tree->get_root_fullpath(); + } + return _parent->get_fullpath() + "/" + _dirname; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::get_path +// Access: Public +// Description: Returns the relative pathname to this particular +// directory, as seen from the root of the tree. +//////////////////////////////////////////////////////////////////// +string CVSSourceDirectory:: +get_path() const { + if (_parent == (CVSSourceDirectory *)NULL) { + return _dirname; + } + return _parent->get_path() + "/" + _dirname; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::get_rel_to +// Access: Public +// Description: Returns the relative path to the other directory from +// this one. This does not include a trailing slash. +//////////////////////////////////////////////////////////////////// +string CVSSourceDirectory:: +get_rel_to(const CVSSourceDirectory *other) const { + const CVSSourceDirectory *a = this; + const CVSSourceDirectory *b = other; + + if (a == b) { + return "."; + } + + string prefix, postfix; + while (a->_depth > b->_depth) { + prefix += "../"; + a = a->_parent; + assert(a != (CVSSourceDirectory *)NULL); + } + + while (b->_depth > a->_depth) { + postfix = b->_dirname + "/" + postfix; + b = b->_parent; + assert(b != (CVSSourceDirectory *)NULL); + } + + while (a != b) { + prefix += "../"; + postfix = b->_dirname + "/" + postfix; + a = a->_parent; + b = b->_parent; + assert(a != (CVSSourceDirectory *)NULL); + assert(b != (CVSSourceDirectory *)NULL); + } + + string result = prefix + postfix; + assert(!result.empty()); + return result.substr(0, result.length() - 1); +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::get_num_children +// Access: Public +// Description: Returns the number of subdirectories below this +// directory. +//////////////////////////////////////////////////////////////////// +int CVSSourceDirectory:: +get_num_children() const { + return _children.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::get_child +// Access: Public +// Description: Returns the nth subdirectory below this directory. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceDirectory:: +get_child(int n) const { + nassertr(n >= 0 && n < (int)_children.size(), NULL); + return _children[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::find_relpath +// Access: Public +// Description: Returns the source directory that corresponds to the +// given relative path from this directory, or NULL if +// there is no match. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceDirectory:: +find_relpath(const string &relpath) { + if (relpath.empty()) { + return this; + } + + size_t slash = relpath.find('/'); + string first = relpath.substr(0, slash); + string rest; + if (slash != string::npos) { + rest = relpath.substr(slash + 1); + } + + if (first.empty() || first == ".") { + return find_relpath(rest); + + } else if (first == "..") { + if (_parent != NULL) { + return _parent->find_relpath(rest); + } + // Tried to back out past the root directory. + return (CVSSourceDirectory *)NULL; + } + + // Check for a child named "first". + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + if ((*ci)->get_dirname() == first) { + return (*ci)->find_relpath(rest); + } + } + + // No match. + return (CVSSourceDirectory *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::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 *CVSSourceDirectory:: +find_dirname(const string &dirname) { + if (dirname == _dirname) { + return this; + } + + Children::const_iterator ci; + for (ci = _children.begin(); ci != _children.end(); ++ci) { + CVSSourceDirectory *result = (*ci)->find_dirname(dirname); + if (result != (CVSSourceDirectory *)NULL) { + return result; + } + } + + return (CVSSourceDirectory *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceDirectory::scan +// Access: Public +// Description: Recursively scans the contents of the source +// directory. Fullpath is the full path name to the +// directory; key_filename is the name of a file that +// must exist in each subdirectory for it to be +// considered part of the hierarchy. Returns true on +// success, false on failure. +//////////////////////////////////////////////////////////////////// +bool CVSSourceDirectory:: +scan(const string &fullpath, const string &key_filename) { + DIR *root = opendir(fullpath.c_str()); + if (root == (DIR *)NULL) { + cerr << "Unable to scan directory " << fullpath << "\n"; + return false; + } + + struct dirent *d; + d = readdir(root); + while (d != (struct dirent *)NULL) { + string filename = d->d_name; + + if (!filename.empty() && filename[0] != '.') { + // Is this possibly a subdirectory name? + string next_path = fullpath + "/" + filename; + string key = next_path + "/" + key_filename; + if (access(key.c_str(), F_OK) == 0) { + CVSSourceDirectory *subdir = + new CVSSourceDirectory(_tree, this, filename); + _children.push_back(subdir); + + if (!subdir->scan(next_path, key_filename)) { + closedir(root); + return false; + } + + } else { + // It's not a subdirectory; call it a regular file. + _tree->add_file(filename, this); + } + } + + d = readdir(root); + } + closedir(root); + return true; +} diff --git a/pandatool/src/cvscopy/cvsSourceDirectory.h b/pandatool/src/cvscopy/cvsSourceDirectory.h new file mode 100644 index 0000000000..9b0b474b0a --- /dev/null +++ b/pandatool/src/cvscopy/cvsSourceDirectory.h @@ -0,0 +1,52 @@ +// Filename: cvsSourceDirectory.h +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef CVSSOURCEDIRECTORY_H +#define CVSSOURCEDIRECTORY_H + +#include + +#include + +class CVSSourceTree; + +//////////////////////////////////////////////////////////////////// +// Class : CVSSourceDirectory +// Description : This represents one particular directory in the +// hierarchy of source directory files. We must scan +// the source directory to identify where the related +// files have previously been copied. +//////////////////////////////////////////////////////////////////// +class CVSSourceDirectory { +public: + CVSSourceDirectory(CVSSourceTree *tree, CVSSourceDirectory *parent, + const string &dirname); + ~CVSSourceDirectory(); + + string get_dirname() const; + string get_fullpath() const; + string get_path() const; + string get_rel_to(const CVSSourceDirectory *other) const; + + int get_num_children() const; + CVSSourceDirectory *get_child(int n) const; + + CVSSourceDirectory *find_relpath(const string &relpath); + CVSSourceDirectory *find_dirname(const string &dirname); + +public: + bool scan(const string &prefix, const string &key_filename); + +private: + CVSSourceTree *_tree; + CVSSourceDirectory *_parent; + string _dirname; + int _depth; + + typedef vector Children; + Children _children; +}; + +#endif diff --git a/pandatool/src/cvscopy/cvsSourceTree.cxx b/pandatool/src/cvscopy/cvsSourceTree.cxx new file mode 100644 index 0000000000..832f8e6aa7 --- /dev/null +++ b/pandatool/src/cvscopy/cvsSourceTree.cxx @@ -0,0 +1,543 @@ +// Filename: cvsSourceTree.cxx +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "cvsSourceTree.h" +#include "cvsSourceDirectory.h" + +#include +#include + +#include +#include +#include // for perror +#include + +bool CVSSourceTree::_got_start_fullpath = false; +string 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 string &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 string &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 string &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() || + fullpath.substr(0, root_fullpath.length()) != root_fullpath) { + // Nope! + return (CVSSourceDirectory *)NULL; + } + + // The relative name is the part of fullpath not in root_fullpath. + string 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('/'); + string first = relpath.substr(0, slash); + string rest; + if (slash != string::npos) { + rest = relpath.substr(slash + 1); + } + + if (first == _root->get_dirname()) { + 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. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceTree:: +choose_directory(const string &filename, CVSSourceDirectory *suggested_dir, + bool force, bool interactive) { + static Directories empty_dirs; + + Filenames::const_iterator fi; + fi = _filenames.find(filename); + if (fi != _filenames.end()) { + // The filename already exists somewhere. + const Directories &dirs = (*fi).second; + + return prompt_user(filename, suggested_dir, dirs, + force, interactive); + } + + // Now we have to prompt the user for a suitable place to put it. + return prompt_user(filename, suggested_dir, empty_dirs, + force, interactive); +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceTree::get_root_fullpath +// Access: Public +// Description: Returns the full path from the root to the top of +// the source hierarchy. +//////////////////////////////////////////////////////////////////// +string CVSSourceTree:: +get_root_fullpath() { + nassertr(!_path.empty(), string()); + 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. +//////////////////////////////////////////////////////////////////// +string CVSSourceTree:: +get_root_dirname() const { + nassertr(_root != (CVSSourceDirectory *)NULL, string()); + 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 &filename, CVSSourceDirectory *dir) { + _filenames[filename].push_back(dir); +} + +//////////////////////////////////////////////////////////////////// +// 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 string &path) { + // We have to call this first to guarantee that we have already + // determined our starting directory. + get_start_fullpath(); + + if (chdir(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() { + string start_fullpath = get_start_fullpath(); + + if (chdir(start_fullpath.c_str()) < 0) { + // Hey! We can't get back to the directory we started from! + perror(start_fullpath.c_str()); + cerr << "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. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceTree:: +prompt_user(const string &filename, CVSSourceDirectory *suggested_dir, + const CVSSourceTree::Directories &dirs, + bool force, bool interactive) { + if (dirs.size() == 1) { + // The file already exists in exactly one place. + if (interactive) { + return dirs[0]; + } + CVSSourceDirectory *result = ask_existing(filename, dirs[0]); + if (result != (CVSSourceDirectory *)NULL) { + return result; + } + + } else if (dirs.size() > 1) { + // The file already exists in multiple places. + if (force && !interactive) { + return dirs[0]; + } + CVSSourceDirectory *result = ask_existing(filename, dirs, suggested_dir); + if (result != (CVSSourceDirectory *)NULL) { + return result; + } + } + + // The file does not already exist, or the user declined to replace + // an existing file. + if (force && !interactive) { + return suggested_dir; + } + + if (find(dirs.begin(), dirs.end(), suggested_dir) == dirs.end()) { + CVSSourceDirectory *result = ask_new(filename, suggested_dir); + if (result != (CVSSourceDirectory *)NULL) { + return result; + } + } + + // Ask the user where the damn thing should go. + return ask_any(filename); +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceTree::ask_existing +// Access: Private +// Description: Asks the user if he wants to replace an existing +// file. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceTree:: +ask_existing(const string &filename, CVSSourceDirectory *dir) { + while (true) { + nout << filename << " found in tree at " + << dir->get_path() + "/" + filename << ".\n"; + string result = prompt("Overwrite this file (y/n)? "); + nassertr(!result.empty(), (CVSSourceDirectory *)NULL); + if (result.size() == 1) { + if (tolower(result[0]) == 'y') { + return dir; + } else if (tolower(result[0]) == 'n') { + return NULL; + } + } + + 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. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceTree:: +ask_existing(const string &filename, const CVSSourceTree::Directories &dirs, + CVSSourceDirectory *suggested_dir) { + while (true) { + nout << filename << " found in tree at more than one place:\n"; + + bool any_suggested = false; + for (int i = 0; i < (int)dirs.size(); i++) { + nout << " " << (i + 1) << ". " + << dirs[i]->get_path() + "/" + filename << "\n"; + if (dirs[i] == suggested_dir) { + any_suggested = true; + } + } + + int next_option = dirs.size() + 1; + int suggested_option = -1; + + if (!any_suggested) { + suggested_option = next_option; + next_option++; + nout << "\n" << suggested_option + << ". create " + << suggested_dir->get_path() + "/" + filename + << "\n"; + } + + int other_option = next_option; + nout << other_option << ". Other\n"; + + string result = prompt("Choose an option: "); + nassertr(!result.empty(), (CVSSourceDirectory *)NULL); + const char *nptr = result.c_str(); + char *endptr; + int option = strtol(nptr, &endptr, 10); + if (*endptr == '\0') { + if (option >= 1 && option <= (int)dirs.size()) { + return dirs[option - 1]; + + } else if (option == suggested_option) { + return suggested_dir; + + } else if (option == other_option) { + return NULL; + } + } + + nout << "*** Invalid response: " << result << "\n\n"; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceTree::ask_new +// Access: Private +// Description: Asks the user if he wants to replace an existing +// file. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceTree:: +ask_new(const string &filename, CVSSourceDirectory *dir) { + while (true) { + nout << filename << " will be created at " + << dir->get_path() + "/" + filename << ".\n"; + string result = prompt("Create this file (y/n)? "); + nassertr(!result.empty(), (CVSSourceDirectory *)NULL); + if (result.size() == 1) { + if (tolower(result[0]) == 'y') { + return dir; + } else if (tolower(result[0]) == 'n') { + return NULL; + } + } + + 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. +//////////////////////////////////////////////////////////////////// +CVSSourceDirectory *CVSSourceTree:: +ask_any(const string &filename) { + while (true) { + string result = + prompt("Enter the name of the directory to copy " + filename + " to: "); + nassertr(!result.empty(), (CVSSourceDirectory *)NULL); + + // 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) { + return dir; + } + + 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. It does this essentially by cd'ing +// to the directory and doing pwd, then cd'ing back. +// Returns the empty string if the directory is invalid +// or cannot be cd'ed into. +//////////////////////////////////////////////////////////////////// +string CVSSourceTree:: +get_actual_fullpath(const string &path) { + if (!temp_chdir(path)) { + return string(); + } + + string cwd = get_cwd(); + restore_cwd(); + + return cwd; +} + + +//////////////////////////////////////////////////////////////////// +// 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. +//////////////////////////////////////////////////////////////////// +string CVSSourceTree:: +get_start_fullpath() { + if (!_got_start_fullpath) { + _start_fullpath = get_cwd(); + _got_start_fullpath = true; + } + return _start_fullpath; +} + +//////////////////////////////////////////////////////////////////// +// Function: CVSSourceTree::get_cwd +// Access: Private, Static +// Description: Calls the system getcwd(), automatically allocating a +// large enough string. +//////////////////////////////////////////////////////////////////// +string CVSSourceTree:: +get_cwd() { + static size_t bufsize = 1024; + static char *buffer = NULL; + + if (buffer == (char *)NULL) { + buffer = new char[bufsize]; + } + + while (getcwd(buffer, bufsize) == (char *)NULL) { + if (errno != ERANGE) { + perror("getcwd"); + return string(); + } + delete[] buffer; + bufsize = bufsize * 2; + buffer = new char[bufsize]; + nassertr(buffer != (char *)NULL, string()); + } + + return string(buffer); +} diff --git a/pandatool/src/cvscopy/cvsSourceTree.h b/pandatool/src/cvscopy/cvsSourceTree.h new file mode 100644 index 0000000000..e2e7081957 --- /dev/null +++ b/pandatool/src/cvscopy/cvsSourceTree.h @@ -0,0 +1,81 @@ +// Filename: cvsSourceTree.h +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef CVSSOURCETREE_H +#define CVSSOURCETREE_H + +#include + +#include +#include + +class CVSSourceDirectory; + +//////////////////////////////////////////////////////////////////// +// Class : CVSSourceTree +// Description : This represents the root of the tree of source +// directory files. +//////////////////////////////////////////////////////////////////// +class CVSSourceTree { +public: + CVSSourceTree(); + ~CVSSourceTree(); + + void set_root(const string &root_path); + bool scan(const string &key_filename); + + CVSSourceDirectory *get_root() const; + CVSSourceDirectory *find_directory(const string &path); + CVSSourceDirectory *find_relpath(const string &relpath); + CVSSourceDirectory *find_dirname(const string &dirname); + + CVSSourceDirectory *choose_directory(const string &filename, + CVSSourceDirectory *suggested_dir, + bool force, bool interactive); + + string get_root_fullpath(); + string get_root_dirname() const; + + static bool temp_chdir(const string &path); + static void restore_cwd(); + +public: + void add_file(const string &filename, CVSSourceDirectory *dir); + +private: + typedef vector Directories; + + CVSSourceDirectory * + prompt_user(const string &filename, CVSSourceDirectory *suggested_dir, + const Directories &dirs, bool force, bool interactive); + + CVSSourceDirectory *ask_existing(const string &filename, + CVSSourceDirectory *dir); + CVSSourceDirectory *ask_existing(const string &filename, + const Directories &dirs, + CVSSourceDirectory *suggested_dir); + CVSSourceDirectory *ask_new(const string &filename, CVSSourceDirectory *dir); + CVSSourceDirectory *ask_any(const string &filename); + + string prompt(const string &message); + + static string get_actual_fullpath(const string &path); + static string get_start_fullpath(); + static string get_cwd(); + +private: + string _path; + CVSSourceDirectory *_root; + + typedef map Filenames; + Filenames _filenames; + + static bool _got_start_fullpath; + static string _start_fullpath; + bool _got_root_fullpath; + string _root_fullpath; +}; + +#endif diff --git a/pandatool/src/cvscopy/testCopy.cxx b/pandatool/src/cvscopy/testCopy.cxx new file mode 100644 index 0000000000..6805a62840 --- /dev/null +++ b/pandatool/src/cvscopy/testCopy.cxx @@ -0,0 +1,94 @@ +// Filename: testCopy.cxx +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "testCopy.h" +#include "cvsSourceDirectory.h" + +//////////////////////////////////////////////////////////////////// +// Function: TestCopy::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +TestCopy:: +TestCopy() { + set_program_description + ("This program copies one or more files into a CVS source hierarchy. " + "Rather than copying the named files immediately into the current " + "directory, it first scans the entire source hierarchy, identifying all " + "the already-existing files. If the named file to copy matches the " + "name of an already-existing file in the current directory or elsewhere " + "in the hierarchy, that file is overwritten.\n\n" + + "This is primarily useful as a test program for libcvscopy."); +} + +//////////////////////////////////////////////////////////////////// +// Function: TestCopy::run +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +void TestCopy:: +run() { + SourceFiles::iterator fi; + for (fi = _source_files.begin(); fi != _source_files.end(); ++fi) { + CVSSourceDirectory *dest = import(*fi, 0, _model_dir); + if (dest == (CVSSourceDirectory *)NULL) { + exit(1); + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: TestCopy::copy_file +// Access: Protected, Virtual +// Description: Called by import() if the timestamps indicate that a +// file needs to be copied. This does the actual copy +// of a file from source to destination. If new_file is +// true, then dest does not already exist. +//////////////////////////////////////////////////////////////////// +bool TestCopy:: +copy_file(Filename source, Filename dest, + CVSSourceDirectory *dir, int type, bool new_file) { + source.set_binary(); + dest.set_binary(); + + ifstream in; + ofstream out; + if (!source.open_read(in)) { + cerr << "Cannot read " << source << "\n"; + return false; + } + + if (!dest.open_write(out)) { + cerr << "Cannot write " << dest << "\n"; + return false; + } + + int c; + c = in.get(); + while (!in.eof() && !in.fail() && !out.fail()) { + out.put(c); + c = in.get(); + } + + if (in.fail()) { + cerr << "Error reading " << source << "\n"; + return false; + } + if (out.fail()) { + cerr << "Error writing " << dest << "\n"; + return false; + } + + return true; +} + + +int main(int argc, char *argv[]) { + TestCopy prog; + prog.parse_command_line(argc, argv); + prog.run(); + return 0; +} diff --git a/pandatool/src/cvscopy/testCopy.h b/pandatool/src/cvscopy/testCopy.h new file mode 100644 index 0000000000..443e442729 --- /dev/null +++ b/pandatool/src/cvscopy/testCopy.h @@ -0,0 +1,29 @@ +// Filename: testCopy.h +// Created by: drose (31Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef TESTCOPY_H +#define TESTCOPY_H + +#include + +#include "cvsCopy.h" + +//////////////////////////////////////////////////////////////////// +// Class : TestCopy +// Description : A program to copy ordinary files into the cvs tree. +// Mainly to test CVSCopy. +//////////////////////////////////////////////////////////////////// +class TestCopy : public CVSCopy { +public: + TestCopy(); + + void run(); + +protected: + virtual bool copy_file(Filename source, Filename dest, + CVSSourceDirectory *dir, int type, bool new_file); +}; + +#endif diff --git a/pandatool/src/progbase/programBase.cxx b/pandatool/src/progbase/programBase.cxx index 6e898e7396..86beee44cd 100644 --- a/pandatool/src/progbase/programBase.cxx +++ b/pandatool/src/progbase/programBase.cxx @@ -73,6 +73,7 @@ ProgramBase() { _next_sequence = 0; _sorted_options = false; + _last_newline = false; _got_terminal_width = false; _got_option_indent = false; @@ -171,7 +172,8 @@ show_text(const string &prefix, int indent_width, string text) { // This is correct! It goes go to cerr, not to nout. Sending it to // nout would be cyclic, since nout is redefined to map back through // this function. - format_text(cerr, prefix, indent_width, text, _terminal_width); + format_text(cerr, _last_newline, + prefix, indent_width, text, _terminal_width); } //////////////////////////////////////////////////////////////////// @@ -679,12 +681,19 @@ handle_help_option(const string &, const string &, void *) { // // An embedded newline character ('\n') forces a line // break, while an embedded carriage-return character -// ('\r') marks a paragraph break, which is usually -// printed as a blank line. Redundant newline and -// carriage-return characters are generally ignored. +// ('\r'), or two or more consecutive newlines, marks a +// paragraph break, which is usually printed as a blank +// line. Redundant newline and carriage-return +// characters are generally ignored. +// +// The flag last_newline should be initialized to false +// for the first call to format_text, and then preserved +// for future calls; it tracks the state of trailing +// newline characters between calls so we can correctly +// identify doubled newlines. //////////////////////////////////////////////////////////////////// void ProgramBase:: -format_text(ostream &out, +format_text(ostream &out, bool &last_newline, const string &prefix, int indent_width, const string &text, int line_width) { indent_width = min(indent_width, line_width - 20); @@ -705,7 +714,9 @@ format_text(ostream &out, // Skip any initial whitespace and newlines. while (p < text.length() && isspace(text[p])) { - if (text[p] == '\r') { + if (text[p] == '\r' || + (p > 0 && text[p] == '\n' && text[p - 1] == '\n') || + (p == 0 && text[p] == '\n' && last_newline)) { if (!initial_break) { // Here's an initial paragraph break, however. out << "\n"; @@ -724,6 +735,8 @@ format_text(ostream &out, p++; } + last_newline = (!text.empty() && text[text.length() - 1] == '\n'); + while (p < text.length()) { // Look for the paragraph or line break--the next newline // character, if any. @@ -731,8 +744,11 @@ format_text(ostream &out, bool is_paragraph_break = false; if (par == string::npos) { par = text.length(); + /* + This shouldn't be necessary. } else { is_paragraph_break = (text[par] == '\r'); + */ } indent(out, indent_amount); @@ -771,7 +787,8 @@ format_text(ostream &out, // Skip additional whitespace between the lines. while (p < text.length() && isspace(text[p])) { - if (text[p] == '\r') { + if (text[p] == '\r' || + (p > 0 && text[p] == '\n' && text[p - 1] == '\n')) { is_paragraph_break = true; } p++; @@ -780,6 +797,11 @@ format_text(ostream &out, if (eol == par && is_paragraph_break) { // Print the paragraph break as a blank line. out << "\n"; + if (p >= text.length()) { + // If we end on a paragraph break, don't try to insert a new + // one in the next pass. + last_newline = false; + } } indent_amount = indent_width; diff --git a/pandatool/src/progbase/programBase.h b/pandatool/src/progbase/programBase.h index 6b9ef1041c..044735eca2 100644 --- a/pandatool/src/progbase/programBase.h +++ b/pandatool/src/progbase/programBase.h @@ -68,7 +68,7 @@ protected: bool handle_help_option(const string &opt, const string &arg, void *); - static void format_text(ostream &out, + static void format_text(ostream &out, bool &last_newline, const string &prefix, int indent_width, const string &text, int line_width); @@ -107,6 +107,7 @@ private: typedef map GotOptions; GotOptions _got_options; + bool _last_newline; int _terminal_width; bool _got_terminal_width; int _option_indent;