diff --git a/pandatool/src/cvscopy/cvsCopy.cxx b/pandatool/src/cvscopy/cvsCopy.cxx index 89df70c458..2911341d4c 100644 --- a/pandatool/src/cvscopy/cvsCopy.cxx +++ b/pandatool/src/cvscopy/cvsCopy.cxx @@ -35,7 +35,8 @@ CVSCopy() { 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.", + "The default is only to prompt the user when an action is ambiguous " + "or unusual.", &CVSCopy::dispatch_none, &_interactive); add_option diff --git a/pandatool/src/cvscopy/cvsSourceDirectory.cxx b/pandatool/src/cvscopy/cvsSourceDirectory.cxx index 629e6a0661..cc4641e6d7 100644 --- a/pandatool/src/cvscopy/cvsSourceDirectory.cxx +++ b/pandatool/src/cvscopy/cvsSourceDirectory.cxx @@ -16,7 +16,6 @@ #else #include #include -#include #endif //////////////////////////////////////////////////////////////////// diff --git a/pandatool/src/fltprogs/fltCopy.cxx b/pandatool/src/fltprogs/fltCopy.cxx index 6a46c96deb..792b740664 100644 --- a/pandatool/src/fltprogs/fltCopy.cxx +++ b/pandatool/src/fltprogs/fltCopy.cxx @@ -19,7 +19,7 @@ FltCopy:: FltCopy() { 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. " "Rather than copying the named files immediately into the current " "directory, it first scans the entire source hierarchy, identifying all " diff --git a/pandatool/src/softprogs/Sources.pp b/pandatool/src/softprogs/Sources.pp new file mode 100644 index 0000000000..fc516c45af --- /dev/null +++ b/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 diff --git a/pandatool/src/softprogs/softCVS.cxx b/pandatool/src/softprogs/softCVS.cxx new file mode 100644 index 0000000000..b04a22f901 --- /dev/null +++ b/pandatool/src/softprogs/softCVS.cxx @@ -0,0 +1,522 @@ +// Filename: softCVS.cxx +// Created by: drose (10Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "softCVS.h" + +#include +#include +#include + +#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 +#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::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 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 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::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::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::const_iterator begin, + vector::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::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::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 &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 &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; +} diff --git a/pandatool/src/softprogs/softCVS.h b/pandatool/src/softprogs/softCVS.h new file mode 100644 index 0000000000..20e1face33 --- /dev/null +++ b/pandatool/src/softprogs/softCVS.h @@ -0,0 +1,56 @@ +// Filename: softCVS.h +// Created by: drose (10Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SOFTCVS_H +#define SOFTCVS_H + +#include + +#include "softFilename.h" + +#include + +#include +#include + +//////////////////////////////////////////////////////////////////// +// 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::const_iterator begin, + vector::const_iterator end); + bool scan_cvs(const string &dirname, set &cvs_elements); + void consider_add_cvs(const string &dirname, const string &filename, + const set &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 _scene_files; + set _versioned_files; + +protected: + bool _interactive; + bool _no_cvs; + string _cvs_binary; +}; + +#endif diff --git a/pandatool/src/softprogs/softFilename.cxx b/pandatool/src/softprogs/softFilename.cxx new file mode 100644 index 0000000000..dac9591a2c --- /dev/null +++ b/pandatool/src/softprogs/softFilename.cxx @@ -0,0 +1,230 @@ +// Filename: softFilename.cxx +// Created by: drose (10Nov00) +// +//////////////////////////////////////////////////////////////////// + +#include "softFilename.h" + +#include + +//////////////////////////////////////////////////////////////////// +// 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 ©) : + _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 ©) { + _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; +} diff --git a/pandatool/src/softprogs/softFilename.h b/pandatool/src/softprogs/softFilename.h new file mode 100644 index 0000000000..d2444b1b72 --- /dev/null +++ b/pandatool/src/softprogs/softFilename.h @@ -0,0 +1,48 @@ +// Filename: softFilename.h +// Created by: drose (10Nov00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SOFTFILENAME_H +#define SOFTFILENAME_H + +#include + +//////////////////////////////////////////////////////////////////// +// 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 ©); + void operator = (const SoftFilename ©); + + 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