File Coverage

blib/lib/Data/VString.pm
Criterion Covered Total %
statement 50 55 90.9
branch 15 24 62.5
condition 3 6 50.0
subroutine 10 10 100.0
pod 4 4 100.0
total 82 99 82.8


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

: (','

)*

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