File Coverage

blib/lib/Data/VString.pm
Criterion Covered Total %
statement 49 54 90.7
branch 15 24 62.5
condition 3 6 50.0
subroutine 10 10 100.0
pod 4 4 100.0
total 81 98 82.6


line stmt bran cond sub pod time code
1              
2             package Data::VString;
3              
4 4     4   64957 use 5.008;
  4         10  
5 4     4   14 use strict;
  4         5  
  4         89  
6 4     4   17 use warnings;
  4         10  
  4         331  
7              
8             require Exporter;
9             our @ISA = qw(Exporter);
10             our @EXPORT_OK = qw(
11             parse_vstring
12             format_vstring
13             vstring_satisfy
14             vstring_cmp
15             );
16              
17             our $VERSION = '0.000_003'; # '0.0.3'
18              
19 4     4   18 use Carp qw(carp);
  4         4  
  4         2986  
20              
21             #use encoding 'utf8';
22              
23             =head1 NAME
24              
25             Data::VString - Perl extension to handle v-strings (often used as version strings)
26              
27             =head1 SYNOPSIS
28              
29             use Data::VString qw(parse_vstring format_vstring vstring_cmp vstring_satisfy);
30              
31             # going from '0.0.1' to "\x{0}\x{0}\x{1}" and back
32             $i_vstring = parse_vstring($vstring);
33             $vstring = format_vstring($i_vstring);
34              
35             print 'ok' if vstring_cmp($VERSION, '>=', '0.0.1');
36              
37             my $bool = vstring_satisfy($vstring, $predicate)
38              
39             =head1 DESCRIPTION
40              
41             Most of the time, the so-called version numbers
42             are not really numbers, but tuples of integers
43             like C<'0.2.3'>. With this concept of version,
44             C<'0.1'> is the same as C<'0.01'>. The ordering of
45             such tuples is usually defined by comparing each
46             part. And that makes
47              
48             '0.1' > '0.2'
49             '0.2.1' < '0.1.3'
50             '0.11.10' > '0.10.10.10'
51              
52             and also C<'0.1'> > C<'0.1.0'> (because the first one is shorter).
53             There is also no need to define how many integers to accept
54             in the tuple, with C<'0.0.1.2.34.4.580.20'> being
55             a nice version.
56              
57             Perl had (and still has) this concept as v-strings.
58             They had even deserved a syntax on their own:
59             C or C<100.111.1111> (a literal
60             with two or more dots). But their fate is sealed:
61             in L of 5.8 we read:
62              
63             Note: Version Strings (v-strings) have been deprecated.
64             They will not be available after Perl 5.8. The marginal
65             benefits of v-strings were greatly outweighed by the
66             potential for Surprise and Confusion.
67              
68             This module revives them as a simple module implementation.
69             Version strings are well suited in many version "numbering"
70             schemes and straightforward (if you always remember they
71             are not numbers). In Perl, most of the confusion
72             lies in that C<0.1> as a literal is a number and sorts
73             like a number, while C<0.1.0> is a v-string and sorts
74             like a v-string. Also from L:
75              
76             A literal of the form "v1.20.300.4000" is parsed as a string composed
77             of characters with the specified ordinals. This form, known as
78             v-strings, provides an alternative, more readable way to construct
79             strings, rather than use the somewhat less readable interpolation form
80             "\x{1}\x{14}\x{12c}\x{fa0}". This is useful for representing Unicode
81             strings, and for comparing version "numbers" using the string compari-
82             son operators, "cmp", "gt", "lt" etc. If there are two or more dots in
83             the literal, the leading "v" may be omitted.
84              
85             print v9786; # prints UTF-8 encoded SMILEY, "\x{263a}"
86             print v102.111.111; # prints "foo"
87             print 102.111.111; # same
88              
89             This text reveals how this notion of version as tuple
90             of integers can be represented efficiently if one
91             agreeds that each part is limited to 16 bits (0-65565),
92             which is more than enough for practical software
93             versioning schemes. Converting each part to a Unicode
94             character, the version string ends up like a Unicode
95             string which can be compared with the usual string
96             comparators.
97              
98             Here, functions are provided for converting between v-strings
99             (like C<'6.2.28'>) and their internal representation
100             (C<"\x{6}\x{2}\x{1C}">) and to test them against other
101             v-strings.
102              
103              
104             =over 4
105              
106             =item B
107              
108             $i_vstring = parse_vstring($vstring);
109              
110             parse_vstring('0.1.2') # return "\x{0}\x{1}\x{2}"
111              
112             Converts a v-string into its internal representation
113             (the string made up the Unicode characters given
114             by the ordinals specified in v-string parts).
115              
116             The syntax of a v-string can be defined by the
117             following syntax rule (in C style)
118              
119             : /\d+/ ( /[._]/ /\d+/ )*
120              
121             For the reverse operation, see C.
122              
123             =cut
124              
125             sub _is_vstring {
126 40     40   190 return shift =~ /^\d+([._]\d+)*$/;
127             }
128              
129             sub parse_vstring {
130 40     40 1 45 my $vs = shift;
131 40 100 66     87 return undef unless defined $vs && _is_vstring($vs);
132             #no warnings 'utf8'; # every 16-bit value is ok
133 38         101 $vs =~ s/[._]?(\d+)/chr($1 & 0x0FFFF)/eg;
  62         122  
134 38         72 return $vs
135             }
136              
137             =item B
138              
139             $vstring = format_vstring($i_vstring)
140              
141             Converts the internal representation of a v-string
142             into a readable v-string. It does the reverse
143             operation of C.
144              
145             =cut
146              
147             sub format_vstring {
148 3     3 1 8 my $vs = shift;
149 3 50       6 return $vs unless $vs; # take care of ''
150             #no warnings 'utf8'; # every 16-bit value is ok
151 3         11 $vs =~ s/(.)/ord($1)."."/eg;
  7         16  
152 3         5 chop $vs;
153 3         12 return $vs
154             }
155              
156             =item B
157              
158             vstring_satisfy($vstring, $predicate);
159              
160             vstring_satisfy('0.1.1', '0.1.1'); # true
161             vstring_satisfy('0.1.1', '> 0, < 0.2, != 0.1.0'); # true
162             vstring_satisfy('0.2.4', '0.2.5..0.3.4'); # false
163              
164             Determines if a v-string satisfy a predicate.
165             The predicate is a list of simple predicates,
166             each one must be satisfied (that is, an I).
167             Simple predicates takes one of three forms:
168              
169             '0.1.2' - exact match
170             '>= 3.14.15' - (relational operator) (v-string)
171             '5.6 .. 10.8' - meaning '>= 5.6, <= 10.8'
172              
173             A grammar for predicates in L-like syntax
174             is:
175              
176            

: (','

)*

177              
178             : # the same as '=='
179             |
180             | '..' # the same as ">= , <= "
181              
182             : '==' | '!=' | '<=' | '>=' | '<' | '>'
183              
184             Spaces are irrelevant in predicates.
185              
186             =cut
187              
188             sub vstring_satisfy {
189 7     7 1 11 my $vs = shift;
190 7         7 my $p = shift;
191 7         18 $p =~ s/\s//g; # spaces are irrelevant
192 7         16 my @p = split ',', $p;
193 7         10 for (@p) {
194 9 100       37 if (/^(\d+([._]\d+)*)$/) {
195 3 100       6 next if _vstring_cmp($vs, '==', $1);
196 1         5 return 0;
197             }
198 6 100       16 if (/^([=!<>]=|[<>])(\d+([._]\d+)*)$/) {
199 4 50       6 next if _vstring_cmp($vs, $1, $2);
200 0         0 return 0;
201             }
202 2 50       10 if (/^(\d+([._]\d+)*)\.\.(\d+([._]\d+)*)$/) {
203 2 50 33     3 next if _vstring_cmp($1, '<=', $vs) &&
204             _vstring_cmp($vs, '<=', $3); # !!!
205 0         0 return 0;
206             }
207 0 0       0 carp "bad predicate $_"
208             and return undef;
209             }
210 6         18 return 1;
211             }
212              
213             my %cmp = (
214             '==' => sub { shift eq shift },
215             '!=' => sub { shift ne shift },
216             '<=' => sub { shift le shift },
217             '>=' => sub { shift ge shift },
218             '<' => sub { shift lt shift },
219             '>' => sub { shift gt shift }
220             );
221              
222             #sub Dump_literal {
223             # my $lit = shift;
224             # use YAML;
225             # my $y = YAML::Dump $lit;
226             # $y =~ s/--- //;
227             # $y =~ s/\n//g;
228             # return $y
229             #}
230              
231             sub _vstring_cmp {
232 14     14   23 my $v1 = parse_vstring shift;
233 14         15 my $op = shift; # op is one of '==', '!=', '<=', '>=', '<', '>'
234 14         16 my $v2 = parse_vstring shift;
235             #print "v1: ", Dump_literal($v1),
236             # " op: ", $op,
237             # " v2: ", Dump_literal($v2), "\n";
238 14         12 return &{$cmp{$op}}($v1, $v2);
  14         25  
239             }
240              
241             =item B
242              
243             $ans = vstring_cmp($vs1, $op, $vs2)
244              
245             $eq = vstring_cmp('0.1.02', '==', '0.01.2'); # ok
246             $le = vstring_cmp('1.2.3', '>=', '3.2.1'); # not ok
247              
248             Makes a comparison between two v-strings. The supported operators
249             are '==', '!=', '<=', '>=', '<', and '>'.
250              
251             =cut
252              
253             sub vstring_cmp {
254 3     3 1 12 my $v1 = parse_vstring shift;
255 3 50       6 return undef unless $v1;
256 3         4 my $op = shift;
257 3 50       6 unless (exists $cmp{$op}) {
258 0         0 carp "vstring_cmp: unknown op '$op'";
259             return undef
260 0         0 }
261 3         5 my $v2 = parse_vstring shift;
262 3 50       5 return undef unless $v2;
263 3         3 return &{$cmp{$op}}($v1, $v2);
  3         7  
264             }
265              
266             =back
267              
268              
269             =head2 EXPORT
270              
271             None by default. C, C,
272             C, and C can be exported on demand.
273              
274             =begin comment
275              
276             Rewrite this section (DESCRIPTION) and move citations of
277             perldata to a new section (HISTORY), making the documentation
278             less centered in Perl documentation.
279              
280             Document also the use of '_' as version part separator.
281             (A usual convention used in CPAN is that when a version
282             string contains '_', it is meant to be a developer's version).
283              
284             Remember also the syntactical confusion that 'v65' is not
285             a v-string in a the right hand of C<< '=>' >>.
286              
287             Include a link to the JSAN library when it is released.
288              
289             Document the behavior on error of the functions of the module.
290              
291             =end comment
292              
293              
294             =cut
295              
296             =head1 SEE ALSO
297              
298             L
299              
300             L by John Peacock. That module is older and more famous.
301             The main differences are:
302              
303             =over 4
304              
305             =item *
306              
307             C is OO, this module is a bunch of functions
308              
309             =item *
310              
311             C does not represents version as Unicode strings
312             as we do (well, I think so after a quick glance of the code)
313              
314             =item *
315              
316             C is much more tolerant with numeric versions.
317             This module is not concerned with backward compatibility.
318             Use it versions as strings from the beginning,
319             stay out of trouble with numeric versions.
320              
321             =item *
322              
323             C is also more tolerant with non-numeric versions.
324             On the contrary, C is very strict about
325             syntax.
326              
327             =item *
328              
329             we don't dare to redefine C.
330              
331             =item *
332              
333             v-strings are treated as data here and no attempt
334             to force semantics as Perl module version was made.
335             Indeed I started coding this module for
336             handling JSAN distributions (which are data from
337             the point of view of the Perl program).
338              
339             =back
340              
341             This module is a companion for the JSAN module
342             C. This one implements the Perl side
343             while the other will implement the JavaScript side.
344              
345             =head1 BUGS
346              
347             There must be some. Because all trivial software
348             must have at least one bug. This is the actual
349             list of known bugs.
350              
351             =over 4
352              
353             =item *
354              
355             There is a bug with certain version parts which are
356             illegal Unicode characters. So the full range
357             (0..65535) is not actually usable.
358              
359             =back
360              
361             Please report bugs via CPAN RT L.
362              
363             =head1 AUTHOR
364              
365             Adriano R. Ferreira, Eferreira@cpan.orgE
366              
367             =head1 COPYRIGHT AND LICENSE
368              
369             Copyright (C) 2005 by Adriano R. Ferreira
370              
371             This library is free software; you can redistribute it and/or modify
372             it under the same terms as Perl itself.
373              
374              
375             =cut
376              
377             1;
378