about summary refs log tree commit diff
path: root/docs/tool/Modules/NaturalDocs/Version.pm
blob: 36d2d88c933989af6bd79365d5008233d1a0dc52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
###############################################################################
#
#   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 <VersionInt> 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 <VersionInt>.
#
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 <VersionInt> to a text file.
#
#   Parameters:
#
#       fileHandle - The handle of the file to write it to.  It should be at the correct location.
#       version - The <VersionInt> to write.
#
sub ToTextFile #(handle fileHandle, VersionInt version)
    {
    my ($self, $fileHandle, $version) = @_;

    print $fileHandle $self->ToString($version) . "\n";
    };


#
#   Function: FromTextFile
#
#   Retrieves a <VersionInt> from a text file.
#
#   Parameters:
#
#       fileHandle - The handle of the file to read it from.  It should be at the correct location.
#
#   Returns:
#
#       The <VersionInt>.
#
sub FromTextFile #(handle fileHandle) => VersionInt
    {
    my ($self, $fileHandle) = @_;

    my $version = <$fileHandle>;
    ::XChomp(\$version);

    return $self->FromString($version);
    };


#
#   Function: ToBinaryFile
#
#   Writes a <VersionInt> to a binary file.
#
#   Parameters:
#
#       fileHandle - The handle of the file to write it to.  It should be at the correct location.
#       version - The <VersionInt> 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 <VersionInt> from a binary file.
#
#   Parameters:
#
#       fileHandle - The handle of the file to read it from.  It should be at the correct location.
#
#   Returns:
#
#       The <VersionInt>.
#
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 <VersionInt> 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 <VersionInt> 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 <VersionInt>.
#
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 <VersionInt> of the file format.
#       minimumVersion - The minimum <VersionInt> 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 <VersionInt> 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
#
#   <VersionInts> 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 <String Format> followed by a native line break.
#


1;