############################################################################### # # Package: NaturalDocs::Version # ############################################################################### # # A package for handling version information. What? That's right. Although it should be easy and obvious, version numbers # need to be dealt with in a variety of formats, plus there's compatibility with older releases which handled it differently. I # wanted to centralize the code after it started getting complicated. So there ya go. # ############################################################################### # This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure # Natural Docs is licensed under the GPL use strict; use integer; package NaturalDocs::Version; ############################################################################### # Group: Functions # # Function: ToString # # Converts a to a string. # sub ToString #(VersionInt version) => string { my ($self, $version) = @_; my ($major, $minor, $month, $day, $year) = $self->ToValues($version); if ($minor % 10 == 0) { $minor /= 10; }; if ($day) { return sprintf('Development Release %02d-%02d-%d (%d.%d base)', $month, $day, $year, $major, $minor); } else { return $major . '.' . $minor; }; }; # # Function: FromString # # Converts a version string to a . # sub FromString #(string string) => VersionInt { my ($self, $string) = @_; if ($string eq '1') { return $self->FromValues(0, 91, 0, 0, 0); # 0.91 } else { my ($major, $minor, $month, $day, $year); if ($string =~ /^(\d{1,2})\.(\d{1,2})$/) { ($major, $minor) = ($1, $2); ($month, $day, $year) = (0, 0, 0); } elsif ($string =~ /^Development Release (\d{1,2})-(\d{1,2})-(\d\d\d\d) \((\d{1,2})\.(\d{1,2}) base\)$/) { ($month, $day, $year, $major, $minor) = ($1, $2, $3, $4, $5); # We have to do sanity checking because these can come from user-editable text files. The version numbers should # already be constrained simply by being forced to have only two digits. if ($month > 12 || $month < 1 || $day > 31 || $day < 1 || $year > 2255 || $year < 2000) { die 'The version string ' . $string . " doesn't have a valid date.\n"; }; } else { die 'The version string ' . $string . " isn't in a recognized format.\n"; }; if (length $minor == 1) { $minor *= 10; }; return $self->FromValues($major, $minor, $month, $day, $year); }; }; # # Function: ToTextFile # # Writes a to a text file. # # Parameters: # # fileHandle - The handle of the file to write it to. It should be at the correct location. # version - The to write. # sub ToTextFile #(handle fileHandle, VersionInt version) { my ($self, $fileHandle, $version) = @_; print $fileHandle $self->ToString($version) . "\n"; }; # # Function: FromTextFile # # Retrieves a from a text file. # # Parameters: # # fileHandle - The handle of the file to read it from. It should be at the correct location. # # Returns: # # The . # sub FromTextFile #(handle fileHandle) => VersionInt { my ($self, $fileHandle) = @_; my $version = <$fileHandle>; ::XChomp(\$version); return $self->FromString($version); }; # # Function: ToBinaryFile # # Writes a to a binary file. # # Parameters: # # fileHandle - The handle of the file to write it to. It should be at the correct location. # version - The to write. # sub ToBinaryFile #(handle fileHandle, VersionInt version) { my ($self, $fileHandle, $version) = @_; my ($major, $minor, $month, $day, $year) = $self->ToValues($version); # 1.35 development releases are encoded as 1.36. Everything else is literal. if ($day && $major == 1 && $minor == 35) { $minor = 36; }; print $fileHandle pack('CC', $major, $minor); # Date fields didn't exist with 1.35 stable and earlier. 1.35 development releases are encoded as 1.36, so this works. if ($major > 1 || ($major == 1 && $minor > 35)) { if ($day) { $year -= 2000; }; print $fileHandle pack('CCC', $month, $day, $year); }; }; # # Function: FromBinaryFile # # Retrieves a from a binary file. # # Parameters: # # fileHandle - The handle of the file to read it from. It should be at the correct location. # # Returns: # # The . # sub FromBinaryFile #(handle fileHandle) => VersionInt { my ($self, $fileHandle) = @_; my ($major, $minor, $month, $day, $year); my $raw; read($fileHandle, $raw, 2); ($major, $minor) = unpack('CC', $raw); # 1.35 stable is the last release without the date fields. 1.35 development releases are encoded as 1.36, so this works. if ($major > 1 || ($major == 1 && $minor > 35)) { read($fileHandle, $raw, 3); ($month, $day, $year) = unpack('CCC', $raw); if ($day) { $year += 2000; }; } else { ($month, $day, $year) = (0, 0, 0); }; # Fix the 1.35 development release special encoding. if ($major == 1 && $minor == 36) { $minor = 35; }; return $self->FromValues($major, $minor, $month, $day, $year); }; # # Function: ToValues # # Converts a to the array ( major, minor, month, day, year ). The minor version will be in two digit form, so x.2 # will return 20. The date fields will be zero for stable releases. # sub ToValues #(VersionInt version) => ( int, int, int, int, int ) { my ($self, $version) = @_; my $major = ($version & 0x00003F80) >> 7; my $minor = ($version & 0x0000007F); my $month = ($version & 0x00780000) >> 19; my $day = ($version & 0x0007C000) >> 14; my $year = ($version & 0x7F800000) >> 23; if ($year) { $year += 2000; }; return ( $major, $minor, $month, $day, $year ); }; # # Function: FromValues # # Returns a created from the passed values. # # Parameters: # # major - The major version number. For development releases, it should be the stable version it's based off of. # minor - The minor version number. It should always be two digits, so x.2 should pass 20. For development # releases, it should be the stable version it's based off of. # month - The numeric month of the development release. For stable releases it should be zero. # day - The day of the development release. For stable releases it should be zero. # year - The year of the development release. For stable releases it should be zero. # # Returns: # # The . # sub FromValues #(int major, int minor, int month, int day, int year) => VersionInt { my ($self, $major, $minor, $month, $day, $year) = @_; if ($day) { $year -= 2000; }; return ($major << 7) + ($minor) + ($month << 19) + ($day << 14) + ($year << 23); }; # # Function: CheckFileFormat # # Checks if a file's format is compatible with the current release. # # - If the application is a development release or the file is from one, this only returns true if they are from the exact same # development release. # - If neither of them are development releases, this only returns true if the file is from a release between the minimum specified # and the current version. If there's no minimum it just checks that it's below the current version. # # Parameters: # # fileVersion - The of the file format. # minimumVersion - The minimum required of the file format. May be undef. # # Returns: # # Whether the file's format is compatible per the above rules. # sub CheckFileFormat #(VersionInt fileVersion, optional VersionInt minimumVersion) => bool { my ($self, $fileVersion, $minimumVersion) = @_; my $appVersion = NaturalDocs::Settings->AppVersion(); if ($self->IsDevelopmentRelease($appVersion) || $self->IsDevelopmentRelease($fileVersion)) { return ($appVersion == $fileVersion); } elsif ($minimumVersion && $fileVersion < $minimumVersion) { return 0; } else { return ($fileVersion <= $appVersion); }; }; # # Function: IsDevelopmentRelease # # Returns whether the passed is for a development release. # sub IsDevelopmentRelease #(VersionInt version) => bool { my ($self, $version) = @_; # Return if any of the date fields are set. return ($version & 0x7FFFC000); }; ############################################################################### # Group: Implementation # # About: String Format # # Full Releases: # # Full releases are in the common major.minor format. Either part can be up to two digits. The minor version is interpreted # as decimal places, so 1.3 > 1.22. There are no leading or trailing zeroes. # # Development Releases: # # Development releases are in the format "Development Release mm-dd-yyyy (vv.vv base)" where vv.vv is the version # number of the full release it's based off of. The month and day will have leading zeroes where applicable. Example: # "Development Release 07-09-2006 (1.35 base)". # # 0.91 and Earlier: # # Text files from releases prior to 0.95 had a separate file format version number that was used instead of the application # version. These were never changed between 0.85 and 0.91, so they are simply "1". Text version numbers that are "1" # instead of "1.0" will be interpreted as 0.91. # # # About: Integer Format # # are 32-bit values with the bit distribution below. # # > s yyyyyyyy mmmm ddddd vvvvvvv xxxxxxx # > [syyy|yyyy] [ymmm|mddd] [ddvv|vvvv] [vxxx|xxxx] # # s - The sign bit. Always zero, so it's always interpreted as positive. # y - The year bits if it's a development release, zero otherwise. 2000 is added to the value, so the range is from 2000 to 2255. # m - The month bits if it's a development release, zero otherwise. # d - The day bits if it's a development release, zero otherwise. # v - The major version bits. For development releases, it's the last stable version it was based off of. # x - The minor version bits. It's always stored as two decimals, so x.2 would store 20 here. For development releases, it's the # last stable version it was based off of. # # It's stored with the development release date at a higher significance than the version because we want a stable release to # always treat a development release as higher than itself, and thus not attempt to read any of the data files. I'm not tracking # data file formats at the development release level. # # # About: Binary File Format # # Current: # # Five 8-bit unsigned values, appearing major, minor, month, day, year. Minor is always stored with two digits, so x.2 would # store 20. Year is stored minus 2000, so 2006 is stored 6. Stable releases store zero for all the date fields. # # 1.35 Development Releases: # # 1.35-based development releases are stored the same as current releases, but with 1.36 as the version number. This is # done so previous versions of Natural Docs that didn't include the date fields would still know it's a higher version. There is # no actual 1.36 release. # # 1.35 and Earlier: # # Two 8-bit unsigned values, appearing major then minor. Minor is always stored with two digits, so x.2 would store 20. # # # About: Text File Format # # In text files, versions are the followed by a native line break. # 1;