浏览代码

*** empty log message ***

David Rose 25 年之前
父节点
当前提交
971f547694

+ 2 - 1
pandatool/src/cvscopy/cvsCopy.cxx

@@ -35,7 +35,8 @@ CVSCopy() {
   add_option
   add_option
     ("i", "", 80,
     ("i", "", 80,
      "The opposite of -f, this will prompt the user before each action.  "
      "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.",
+     "The default is only to prompt the user when an action is ambiguous "
+     "or unusual.",
      &CVSCopy::dispatch_none, &_interactive);
      &CVSCopy::dispatch_none, &_interactive);
 
 
   add_option
   add_option

+ 0 - 1
pandatool/src/cvscopy/cvsSourceDirectory.cxx

@@ -16,7 +16,6 @@
 #else
 #else
 #include <sys/types.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <dirent.h>
-#include <unistd.h>
 #endif
 #endif
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
pandatool/src/fltprogs/fltCopy.cxx

@@ -19,7 +19,7 @@
 FltCopy::
 FltCopy::
 FltCopy() {
 FltCopy() {
   set_program_description
   set_program_description
-    ("This program copies one or more MultiGen .flt files into a "
+    ("fltcopy copies one or more MultiGen .flt files into a "
      "CVS source hierarchy.  "
      "CVS source hierarchy.  "
      "Rather than copying the named files immediately into the current "
      "Rather than copying the named files immediately into the current "
      "directory, it first scans the entire source hierarchy, identifying all "
      "directory, it first scans the entire source hierarchy, identifying all "

+ 12 - 0
pandatool/src/softprogs/Sources.pp

@@ -0,0 +1,12 @@
+#begin bin_target
+  #define TARGET softcvs
+  #define LOCAL_LIBS progbase
+
+  #define OTHER_LIBS \
+    express:c pandaexpress:m \
+    dtoolutil:c dconfig:c dtool:m pystub
+
+  #define SOURCES \
+    softCVS.cxx softCVS.h softFilename.cxx softFilename.h
+
+#end bin_target

+ 522 - 0
pandatool/src/softprogs/softCVS.cxx

@@ -0,0 +1,522 @@
+// Filename: softCVS.cxx
+// Created by:  drose (10Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "softCVS.h"
+
+#include <filename.h>
+#include <notify.h>
+#include <vector_string.h>
+
+#include <algorithm>
+
+#ifdef WIN32_VC
+// Windows uses a different API for scanning for files in a directory.
+#define WINDOWS_LEAN_AND_MEAN
+#include <windows.h>
+
+#else
+#include <sys/types.h>
+#include <dirent.h>
+#endif
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SoftCVS::
+SoftCVS() {
+  _cvs_binary = "cvs";
+
+  set_program_description
+    ("softcvs scrubs over a SoftImage database that was recently copied "
+     "into a CVS-controlled directory and prepares it for cvs updating.  "
+     "It eliminates SoftImage's silly filename-based versioning system by "
+     "renaming versioned filenames higher than 1-0 back to version 1-0 "
+     "(thus overwriting the previous file version 1-0).  This allows CVS "
+     "to manage the versioning rather than having to change the filename "
+     "with each new version.  This program also automatically adds each "
+     "new file to the CVS repository.\n\n"
+
+     "You must run this from within the root of a SoftImage database "
+     "directory; e.g. the directory that contains SCENES, PICTURES, MODELS, "
+     "and so on.");
+
+  clear_runlines();
+  add_runline("[opts]");
+
+  add_option
+    ("i", "", 80,
+     "Prompt the user for confirmation before every operation.",
+     &SoftCVS::dispatch_none, &_interactive);
+
+  add_option
+    ("nc", "", 80, 
+     "Do not attempt to add newly-created files to CVS.  The default "
+     "is to add them.",
+     &SoftCVS::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\".",
+     &SoftCVS::dispatch_string, NULL, &_cvs_binary);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::run
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SoftCVS::
+run() {
+  // First, check for the scenes directory.  If it doesn't exist, we
+  // must not be in the root of a soft database.
+  Filename scenes = "SCENES/.";
+  if (!scenes.exists()) {
+    nout << "No SCENES directory found; you are not in the root of a "
+      "SoftImage database.\n";
+    exit(1);
+  }
+
+  // Also, if we're expecting to use CVS, make sure the CVS directory
+  // exists.
+  Filename cvs_entries = "CVS/Entries";
+  if (!_no_cvs && !cvs_entries.exists()) {
+    nout << "You do not appear to be within a CVS-controlled source "
+      "directory.\n";
+    exit(1);
+  }
+
+  // Begin the traversal.
+  traverse(".");
+
+  // Now consider adjusting the scene files.
+  set<string>::iterator si;
+  for (si = _scene_files.begin(); si != _scene_files.end(); ++si) {
+    consider_scene_file(*si);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::traverse
+//       Access: Private
+//  Description: Reads the directory indicated by prefix, looking for
+//               files that are named something like *.2-0.ext,
+//               and renames these to *.1-0.ext.
+////////////////////////////////////////////////////////////////////
+void SoftCVS::
+traverse(const string &dirname) {
+  // Get the list of files in the directory.
+  vector_string files;
+
+  DIR *root = opendir(dirname.c_str());
+  if (root == (DIR *)NULL) {
+    nout << "Unable to scan directory " << dirname << "\n";
+  }
+
+  struct dirent *d;
+  d = readdir(root);
+  while (d != (struct dirent *)NULL) {
+    files.push_back(d->d_name);
+    d = readdir(root);
+  }
+  closedir(root);
+
+  // Now go through and identify files with version numbers, and
+  // collect together those files that are different versions of the
+  // same file.
+  vector<SoftFilename> versions;
+  vector_string::const_iterator fi;
+  for (fi = files.begin(); fi != files.end(); ++fi) {
+    const string &filename = (*fi);
+    if (!filename.empty() && filename[0] != '.') {
+      SoftFilename v(filename);
+      if (v.has_version()) {
+	versions.push_back(v);
+      } else {
+	// Maybe this is a subdirectory?
+	Filename subdir = dirname + "/" + filename;
+	if (subdir.is_directory()) {
+	  traverse(subdir);
+	}
+      }
+    }
+  }
+
+  if (!versions.empty()) {
+    // We actually have some versioned filenames in this directory.
+    // We'll therefore need to know the set of files that are CVS
+    // elements.
+    set<string> cvs_elements;
+    bool in_cvs = false;
+    if (!_no_cvs) {
+      in_cvs = scan_cvs(dirname, cvs_elements);
+    }
+
+    // Now sort the versioned filenames in order so we can scan for
+    // higher versions.
+    sort(versions.begin(), versions.end());
+    
+    vector<SoftFilename>::iterator vi;
+    vi = versions.begin();
+    while (vi != versions.end()) {
+      SoftFilename &file = (*vi);
+      _versioned_files.insert(file.get_base());
+
+      if (!file.is_1_0()) {
+	// Here's a file that needs to be renamed.  But first, identify
+	// all the other versions of the same file.
+	vector<SoftFilename>::iterator start_vi;
+	start_vi = vi;
+	while (vi != versions.end() && (*vi).is_same_file(file)) {
+	  ++vi;
+	}
+	
+	if (rename_file(dirname, start_vi, vi)) {
+	  if (in_cvs) {
+	    consider_add_cvs(dirname, file.get_1_0_filename(), cvs_elements);
+	  }
+
+	  if (file.get_extension() == ".dsc") {
+	    _scene_files.insert(dirname + "/" + file.get_1_0_filename());
+	  }
+	}
+	
+      } else {
+	if (in_cvs) {
+	  consider_add_cvs(dirname, file.get_filename(), cvs_elements);
+	}
+
+	if (file.get_extension() == ".dsc") {
+	  _scene_files.insert(dirname + "/" + file.get_filename());
+	}
+	++vi;
+      }
+    }
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::rename_file
+//       Access: Private
+//  Description: Renames the first file in the indicated list to a
+//               version 1-0 filename, superceding all the other files
+//               in the list.  Returns true if the file is renamed,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool SoftCVS::
+rename_file(const string &dirname,
+	    vector<SoftFilename>::const_iterator begin, 
+	    vector<SoftFilename>::const_iterator end) {
+  int length = end - begin;
+  nassertr(length > 0, false);
+
+  string source_filename = (*begin).get_filename();
+  string dest_filename = (*begin).get_1_0_filename();
+
+  if (length > 2) {
+    nout << source_filename << " supercedes:\n";
+    vector<SoftFilename>::const_iterator p;
+    for (p = begin + 1; p != end; ++p) {
+      nout << "  " << (*p).get_filename() << "\n";
+    }
+
+  } else if (length == 2) {
+    nout << source_filename << " supercedes " 
+	 << (*(begin + 1)).get_filename() << ".\n";
+
+  } else {
+    if (_interactive) {
+      nout << source_filename << " needs renaming.\n";
+    } else {
+      nout << source_filename << " renamed.\n";
+    }
+  }
+
+  if (_interactive) {
+    if (!prompt_yesno("Rename this file (y/n)? ")) {
+      return false;
+    }
+  }
+
+  // Now remove all of the "wrong" files.
+  vector<SoftFilename>::const_iterator p;
+  for (p = begin + 1; p != end; ++p) {
+    Filename file = dirname + "/" + (*p).get_filename();
+    if (!file.unlink()) {
+      nout << "Unable to remove " << file << ".\n";
+    }
+  }
+
+  // And rename the good one.
+  Filename source = dirname + "/" + source_filename;
+  Filename dest = dirname + "/" + dest_filename;
+  if (!source.rename_to(dest)) {
+    nout << "Unable to rename " << source << " to " << dest_filename << ".\n";
+    exit(1);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::scan_cvs
+//       Access: Private
+//  Description: Scans the CVS repository in the indicated directory
+//               to determine which files are already versioned
+//               elements.  Returns true if the directory is
+//               CVS-controlled, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool SoftCVS::
+scan_cvs(const string &dirname, set<string> &cvs_elements) {
+  Filename cvs_entries = dirname + "/CVS/Entries";
+  if (!cvs_entries.exists()) {
+    // Try to CVSify the directory.
+    if (_interactive) {
+      nout << "Directory " << dirname << " is not CVS-controlled.\n";
+      if (!prompt_yesno("Add the directory to CVS (y/n)? ")) {
+	return false;
+      }
+    }
+
+    if (!cvs_add(dirname)) {
+      return false;
+    }
+  }
+
+  ifstream in;
+  cvs_entries.set_text();
+  if (!cvs_entries.open_read(in)) {
+    cerr << "Unable to read CVS directory.\n";
+    return true;
+  }
+
+  string line;
+  getline(in, line);
+  while (!in.fail() && !in.eof()) {
+    if (!line.empty() && line[0] == '/') {
+      size_t slash = line.find('/', 1);
+      if (slash != string::npos) {
+	string filename = line.substr(1, slash - 1);
+	cvs_elements.insert(filename);
+      }
+    }
+
+    getline(in, line);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::consider_add_cvs
+//       Access: Private
+//  Description: Considers adding the indicated file to the CVS
+//               repository, if it is not already there.
+////////////////////////////////////////////////////////////////////
+void SoftCVS::
+consider_add_cvs(const string &dirname, const string &filename,
+		 const set<string> &cvs_elements) {
+  if (cvs_elements.count(filename) != 0) {
+    // Already in CVS!
+    return;
+  }
+
+  string path = dirname + "/" + filename;
+
+  if (_interactive) {
+    if (!prompt_yesno("Add " + path + " to CVS (y/n)? ")) {
+      return;
+    }
+  }
+  
+  cvs_add(path);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::consider_scene_file
+//       Access: Private
+//  Description: Checks to see if the indicated file is a scene file,
+//               and that it contains references to a higher-version
+//               filename.  If so, offers to adjust it.
+////////////////////////////////////////////////////////////////////
+void SoftCVS::
+consider_scene_file(Filename path) {
+  path.set_text();
+  ifstream in;
+  if (!path.open_read(in)) {
+    nout << "Could not read " << path << ".\n";
+    return;
+  }
+  
+  // Scan the scene file into memory.
+  ostringstream scene;
+  if (!scan_scene_file(in, scene)) {
+    // The scene file doesn't need to change.
+    return;
+  }
+  
+  // The scene file should change.
+  if (_interactive) {
+    nout << "Scene file " << path << " needs to be updated.\n";
+    if (!prompt_yesno("Modify this file (y/n)? ")) {
+      return;
+    }
+  }
+  
+  // Rewrite the scene file.
+  in.close();
+  path.unlink();
+  ofstream out;
+  if (!path.open_write(out)) {
+    nout << "Could not write " << path << ".\n";
+    return;
+  }
+  
+  string data = scene.str();
+  out.write(data.data(), data.length());
+  
+  if (out.fail()) {
+    nout << "Error writing " << path << ".\n";
+    return;
+  }
+  nout << "Updated scene file " << path << ".\n";
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::scan_scene_file
+//       Access: Private
+//  Description: Copies a scene file from the input stream to the
+//               output stream, looking for stale file references
+//               (i.e. filenames whose version number is greater than
+//               1-0).  If any such filenames are found, replaces them
+//               with the equivalent 1-0 filename, and returns true;
+//               otherwise, returns false.
+////////////////////////////////////////////////////////////////////
+bool SoftCVS::
+scan_scene_file(istream &in, ostream &out) {
+  bool any_changed = false;
+  int c;
+
+  c = in.get();
+  while (!in.eof() && !in.fail()) {
+    // Skip whitespace.
+    while (isspace(c) && !in.eof() && !in.fail()) {
+      out.put(c);
+      c = in.get();
+    }
+
+    // Now begin a word.
+    string word;
+    while (!isspace(c) && !in.eof() && !in.fail()) {
+      word += c;
+      c = in.get();
+    }
+
+    if (!word.empty()) {
+      // Here's the name of a "versioned" element.  Should we rename
+      // it?  Only if the version is not 1-0, and this kind of element
+      // is versioned by filename.  (Some elements are not versioned
+      // by filename; instead, they keep the same filename but store
+      // multiple versions within themselves.  Trouble.)
+      SoftFilename v(word);
+      if (v.has_version() && !v.is_1_0() &&
+	  _versioned_files.count(v.get_base()) != 0) {
+	out << v.get_1_0_filename();
+	any_changed = true;
+      } else {
+	out << word;
+      }
+    }
+  }
+
+  return any_changed;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::cvs_add
+//       Access: Private
+//  Description: Invokes CVS to add the file to the repository.
+//               Returns true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool SoftCVS::
+cvs_add(const string &path) {
+  string command = _cvs_binary + " add " + path;
+  nout << command << "\n";
+  int result = system(command.c_str());
+
+  if (result != 0) {
+    nout << "Failure invoking cvs.\n";
+    return false;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::prompt_yesno
+//       Access: Private
+//  Description: Asks the user a yes-or-no question.  Returns true if
+//               the answer is yes, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool SoftCVS::
+prompt_yesno(const string &message) {
+  while (true) {
+    string result = prompt(message);
+    nassertr(!result.empty(), false);
+    if (result.size() == 1) {
+      if (tolower(result[0]) == 'y') {
+	return true;
+      } else if (tolower(result[0]) == 'n') {
+	return false;
+      }
+    }
+
+    nout << "*** Invalid response: " << result << "\n\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftCVS::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 SoftCVS::
+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);
+    }
+  }
+}
+
+
+int main(int argc, char *argv[]) {
+  SoftCVS prog;
+  prog.parse_command_line(argc, argv);
+  prog.run();
+  return 0;
+}

+ 56 - 0
pandatool/src/softprogs/softCVS.h

@@ -0,0 +1,56 @@
+// Filename: softCVS.h
+// Created by:  drose (10Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SOFTCVS_H
+#define SOFTCVS_H
+
+#include <pandatoolbase.h>
+
+#include "softFilename.h"
+
+#include <programBase.h>
+
+#include <vector>
+#include <set>
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : SoftCVS
+// Description : This program prepares a SoftImage database for CVS by
+//               renaming everything to version 1-0, and adding new
+//               files to CVS.
+////////////////////////////////////////////////////////////////////
+class SoftCVS : public ProgramBase {
+public:
+  SoftCVS();
+
+  void run();
+
+private:
+  void traverse(const string &dirname);
+
+  bool rename_file(const string &dirname,
+		   vector<SoftFilename>::const_iterator begin, 
+		   vector<SoftFilename>::const_iterator end);
+  bool scan_cvs(const string &dirname, set<string> &cvs_elements);
+  void consider_add_cvs(const string &dirname, const string &filename, 
+			const set<string> &cvs_elements);
+  void consider_scene_file(Filename path);
+  bool scan_scene_file(istream &in, ostream &out);
+
+  bool cvs_add(const string &path);
+
+  bool prompt_yesno(const string &message);
+  string prompt(const string &message);
+
+  set<string> _scene_files;
+  set<string> _versioned_files;
+  
+protected:
+  bool _interactive;
+  bool _no_cvs;
+  string _cvs_binary;
+};
+
+#endif

+ 230 - 0
pandatool/src/softprogs/softFilename.cxx

@@ -0,0 +1,230 @@
+// Filename: softFilename.cxx
+// Created by:  drose (10Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "softFilename.h"
+
+#include <notify.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SoftFilename::
+SoftFilename(const string &filename) :
+  _filename(filename)
+{
+  _has_version = false;
+  _major = 0;
+  _minor = 0;
+
+  // Scan for a version number and an optional extension after each
+  // dot in the filename.
+  size_t dot = _filename.find('.');
+  while (dot != string::npos) {
+    size_t m = dot + 1;
+    const char *fstr = _filename.c_str();
+    char *endptr;
+    // Check for a numeric version number.
+    int major = strtol(fstr + m , &endptr, 10);
+    if (endptr != fstr + m && *endptr == '-') {
+      // We got a major number, is there a minor number?
+      m = (endptr - fstr) + 1;
+      int minor = strtol(fstr + m, &endptr, 10);
+      if (endptr != fstr + m && (*endptr == '.' || *endptr == '\0')) {
+	// We got a minor number too!
+	_has_version = true;
+	_base = _filename.substr(0, dot + 1);
+	_major = major;
+	_minor = minor;
+	_ext = endptr;
+	return;
+      }
+    }
+
+    // That wasn't a version number.  Is there more?
+    dot = _filename.find('.', dot + 1);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+SoftFilename::
+SoftFilename(const SoftFilename &copy) :
+  _filename(copy._filename),
+  _has_version(copy._has_version),
+  _base(copy._base),
+  _major(copy._major),
+  _minor(copy._minor),
+  _ext(copy._ext)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::Copy Assignment operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void SoftFilename::
+operator = (const SoftFilename &copy) {
+  _filename = copy._filename;
+  _has_version = copy._has_version;
+  _base = copy._base;
+  _major = copy._major;
+  _minor = copy._minor;
+  _ext = copy._ext;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_filename
+//       Access: Public
+//  Description: Returns the actual filename as found in the
+//               directory.
+////////////////////////////////////////////////////////////////////
+const string &SoftFilename::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::has_version
+//       Access: Public
+//  Description: Returns true if the filename had a version number,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool SoftFilename::
+has_version() const {
+  return _has_version;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_1_0_filename
+//       Access: Public
+//  Description: Returns what the filename would be if it were version
+//               1-0.
+////////////////////////////////////////////////////////////////////
+string SoftFilename::
+get_1_0_filename() const {
+  nassertr(_has_version, string());
+  return _base + "1-0" + _ext;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_base
+//       Access: Public
+//  Description: Returns the base part of the filename.  This is
+//               everything before the version number.
+////////////////////////////////////////////////////////////////////
+const string &SoftFilename::
+get_base() const {
+  nassertr(_has_version, _filename);
+  return _base;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_major
+//       Access: Public
+//  Description: Returns the major version number.
+////////////////////////////////////////////////////////////////////
+int SoftFilename::
+get_major() const {
+  nassertr(_has_version, 0);
+  return _major;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_minor
+//       Access: Public
+//  Description: Returns the minor version number.
+////////////////////////////////////////////////////////////////////
+int SoftFilename::
+get_minor() const {
+  nassertr(_has_version, 0);
+  return _minor;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_extension
+//       Access: Public
+//  Description: Returns the extension part of the filename.  This is
+//               everything after the version number.
+////////////////////////////////////////////////////////////////////
+const string &SoftFilename::
+get_extension() const {
+  nassertr(_has_version, _ext);
+  return _ext;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::get_non_extension
+//       Access: Public
+//  Description: Returns the filename part, without the extension.
+////////////////////////////////////////////////////////////////////
+string SoftFilename::
+get_non_extension() const {
+  nassertr(_has_version, _filename);
+  nassertr(_ext.length() < _filename.length(), _filename);
+  return _filename.substr(0, _filename.length() - _ext.length());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::is_1_0
+//       Access: Public
+//  Description: Returns true if this is a version 1_0 filename, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool SoftFilename::
+is_1_0() const {
+  nassertr(_has_version, false);
+  return (_major == 1 && _minor == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::is_same_file
+//       Access: Public
+//  Description: Returns true if this file has the same base and
+//               extension as the other, disregarding the version
+//               number; false otherwise.
+////////////////////////////////////////////////////////////////////
+bool SoftFilename::
+is_same_file(const SoftFilename &other) const {
+  return _base == other._base && _ext == other._ext;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SoftFilename::Ordering operator
+//       Access: Public
+//  Description: Puts filenames in order such that the files with the
+//               same base and extension are sorted together; and
+//               within files with the same base and exntension, files
+//               are sorted in decreasing version number order so that
+//               the most recent version appears first.
+//
+//               The ordering operator is only defined for files that
+//               have a version number.
+////////////////////////////////////////////////////////////////////
+bool SoftFilename::
+operator < (const SoftFilename &other) const {
+  nassertr(_has_version, false);
+  nassertr(other._has_version, false);
+
+  if (_base != other._base) {
+    return _base < other._base;
+  }
+  if (_ext != other._ext) {
+    return _ext < other._ext;
+  }
+  if (_major != other._major) {
+    return _major > other._major;
+  }
+  if (_minor != other._minor) {
+    return _minor > other._minor;
+  }
+
+  return false;
+}

+ 48 - 0
pandatool/src/softprogs/softFilename.h

@@ -0,0 +1,48 @@
+// Filename: softFilename.h
+// Created by:  drose (10Nov00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef SOFTFILENAME_H
+#define SOFTFILENAME_H
+
+#include <pandatoolbase.h>
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : SoftFilename
+// Description : This encapsulates a SoftImage versioned filename, of
+//               the form base.v-v.ext: it consists of a base, a major
+//               and minor version number, and an optional extension.
+////////////////////////////////////////////////////////////////////
+class SoftFilename {
+public:
+  SoftFilename(const string &filename);
+  SoftFilename(const SoftFilename &copy);
+  void operator = (const SoftFilename &copy);
+
+  const string &get_filename() const;
+  bool has_version() const;
+
+  string get_1_0_filename() const;
+
+  const string &get_base() const;
+  int get_major() const;
+  int get_minor() const;
+  const string &get_extension() const;
+  string get_non_extension() const;
+
+  bool is_1_0() const;
+
+  bool is_same_file(const SoftFilename &other) const;
+  bool operator < (const SoftFilename &other) const;
+
+private:
+  string _filename;
+  bool _has_version;
+  string _base;
+  int _major;
+  int _minor;
+  string _ext;
+};
+
+#endif