File Coverage

blib/lib/only.pm
Criterion Covered Total %
statement 227 295 76.9
branch 93 150 62.0
condition 27 45 60.0
subroutine 39 41 95.1
pod 9 28 32.1
total 395 559 70.6


line stmt bran cond sub pod time code
1             package only;
2             $VERSION = '0.28';
3 14     14   73 use strict;
  14         31  
  14         455  
4 14     14   234 use 5.006001;
  14         44  
  14         735  
5 14     14   6473 use only::config;
  14         2185  
  14         467  
6 14     14   81 use File::Spec;
  14         28  
  14         281  
7 14     14   71 use Config;
  14         22  
  14         529  
8 14     14   71 use Carp;
  14         29  
  14         1889  
9 14     14   36739 use overload '""' => \&stringify;
  14         33577  
  14         259  
10              
11             BEGIN {
12 14 50   14   2290 *qv = eval {require 'version.pm'} ? \&version::qv : sub{$_[0]};
  14         16787  
  0         0  
13             }
14              
15             # sub X { require Data::Dumper; die Data::Dumper::Dumper(@_) }
16             # sub Y { require Data::Dumper; print Data::Dumper::Dumper(@_) }
17              
18             my $versionlib = '';
19              
20             sub import {
21 26 50 66 26   11227 goto &_install if @_ == 2 and $_[1] eq 'install';
22 26         70 my $class = shift;
23 26         65 my $args = {};
24 26 50 100     303 my $module = (($_[0]||"") =~ /\A!?-?\d/) ? 'perl itself' : shift;
25 26 100 66     586 return unless defined $module and $module;
26 12 100       60 if (ref $module eq 'HASH') {
27 3         6 $args = $module;
28 3   100     27 $module = shift || '';
29             }
30            
31 12         24 my (@sets, $s);
32 12 100       86 if (not @_) {
    50          
33 1         3 @sets = (['']);
34             }
35             elsif (ref($_[0]) eq 'ARRAY') {
36 0         0 @sets = @_;
37             }
38             else {
39 11         45 @sets = ([@_]);
40             }
41              
42              
43 12         32 my $loaded = 0;
44 12         38 for my $set (@sets) {
45 12         77 $s = $class->new;
46 12 100       59 $s->initialize($args, $module, $set) or return;
47 11 50 33     94 if ($module ne 'perl itself' && $s->search) {
48 0         0 $s->include;
49 0         0 local $^W = 0;
50 0         0 eval "require " . $s->module;
51 0 0       0 croak 'Trouble loading ' . $s->found_path . "\n$@" if $@;
52 0         0 fix_INC(); # fix 5.6.1 %INC bug
53 0         0 $loaded = 1;
54 0         0 last;
55             }
56             }
57              
58 11 50       480 if ($module eq 'perl itself') {
    100          
59 0         0 my $perl_version = qv( $] );
60 0         0 my $required_version = $_[0];
61 0         0 $required_version =~ s/!\s*/other than /g;
62 0         0 $required_version =~ s/(?<=\D)-/no later than /g;
63 0         0 $required_version =~ s/-(?=\D)/ or later/g;
64 0 0       0 croak "Perl version $required_version required ",
65             "but this is perl $perl_version.\nstopped"
66             unless $s->check_version($perl_version);
67 0         0 return;
68             }
69             elsif (not defined $INC{$s->canon_path}) {
70 10         89 eval "require " . $s->module;
71 10   100     410 $loaded = not($@) && $s->check_version($s->module->VERSION);
72             }
73              
74 11 100       51 if (not $loaded) {
75 10         66 $s->module_not_found;
76             }
77              
78 1 50       4 my $import = $s->export
79             or return;
80              
81 0         0 @_ = ($s->module, @{$s->arguments});
  0         0  
82 0         0 goto &$import;
83             }
84              
85             sub new {
86 14     14 1 5097 my ($class) = @_;
87 14         43 my $s = bless {}, $class;
88 14         497 $s->found_path('');
89 14   66     208 $s->versionlib($versionlib || &only::config::versionlib);
90 14         33 return $s;
91             }
92              
93             sub initialize {
94 12     12 0 92 my ($s, $args, $module, $set) = @_;
95 12         45 my ($condition, @arguments) = @$set;
96              
97 12 100       178 if (defined $args->{versionlib}) {
98 3         20 $s->versionlib($args->{versionlib});
99 3 100       11 if (not $module) {
100 1         3 only->versionlib($args->{versionlib});
101             }
102             }
103              
104 12 100       2170 return 0 unless $module;
105 11   50     83 $s->module($module || '');
106 11   50     132 $s->condition($condition || '');
107 11         275 $s->arguments(\@arguments);
108              
109             $s->no_export(@arguments == 1 and
110             ref($arguments[0]) eq 'ARRAY' and
111 11   33     319 @{$arguments[0]} == 0
112             );
113              
114 11         51 return 1;
115             }
116              
117             # Try to squish most occurences of a 5.6.1 bug.
118             my ($fix_key, $fix_value) = ('', '');
119             sub fix_INC {
120 13 50   13 1 126 if ($fix_key) {
121 0         0 $INC{$fix_key} = $fix_value;
122 0         0 $fix_key = $fix_value = '';
123             }
124             }
125 9     9   44 INIT { fix_INC }
126              
127              
128             sub only::INC {
129 3     3 0 890 my ($s, $module_path) = @_;
130 3         7 fix_INC;
131 3 50       71 $s->search unless $s->found_path;
132 3 50       12 return unless defined $s->distribution_modules->{$module_path};
133              
134 0         0 my $version = $s->distribution_version;
135              
136 0         0 my $lib_path = File::Spec->catfile($s->versionlib,
137             $version,
138             split('/', $module_path),
139             );
140 0         0 my $arch_path = File::Spec->catfile($s->versionarch,
141             $version,
142             split('/', $module_path),
143             );
144 0         0 for my $path ($lib_path, $arch_path) {
145 0 0       0 if (-f $path) {
146 0 0       0 open my $fh, $path
147             or die "Can't open $path for input\n";
148 0         0 $INC{$module_path} = $path;
149 0         0 ($fix_key, $fix_value) = ($module_path, $path);
150 0         0 return $fh;
151             }
152             }
153 0         0 die "Can't load versioned $module_path\n";
154             }
155              
156             sub search {
157 14     14 1 35 my ($s) = @_;
158 14         309 $s->found_path('');
159              
160 14 100       308 if (defined $INC{$s->canon_path}) {
161 2         13 return $s->check_version($s->get_loaded_version);
162             }
163            
164 12         21 my @versions;
165 12 100       255 if ($s->fancy) {
166 2         11 @versions = grep $s->check_version($_), $s->all_versions();
167             }
168             else {
169 10         53 @versions = map { $_->[0] } @{$s->condition_spec};
  10         71  
  10         511  
170             }
171              
172 12         69 for my $version (sort { $b <=> $a } @versions) {
  0         0  
173 10         39 my $lib_path = File::Spec->catfile($s->versionlib,
174             $version, $s->mod_path);
175 10         289 my $arch_path = File::Spec->catfile($s->versionarch,
176             $version, $s->mod_path);
177 10         58 for my $path ($lib_path, $arch_path) {
178 20 50       580 if (-f $path) {
179 0         0 $s->found_path($path);
180 0         0 $s->distribution_modules($s->found_path);
181 0         0 $s->distribution_version($version);
182 0         0 return 1;
183             }
184             }
185             }
186 12         116 return 0;
187             }
188              
189             sub stringify {
190 2     2 0 5 my ($s) = @_;
191 2         7 'only:' . $s->module . ':' .
192             File::Spec->catdir($s->versionlib, $s->distribution_version)
193             }
194              
195             sub include {
196 4     4 1 15 my ($s) = @_;
197 4         8 $s->remove;
198 4         8 unshift @INC, $s;
199 4         11 $s
200             }
201              
202             sub remove {
203 4     4 1 6 my ($s) = @_;
204 4         14 my $strval = overload::StrVal $s;
205 4 100       22 my @inc = grep {not(ref($_)) or overload::StrVal($_) ne $strval} @INC;
  59         168  
206 4         20 @INC = @inc;
207 4         11 $s
208             }
209              
210             # Generic OO accessors
211             for (qw( found_path mod_path canon_path
212             condition_str condition_spec fancy
213             versionarch arguments no_export
214             distribution_version
215             )) {
216 11 0   11 0 25 eval <
  11 50   56 0 60  
  11 50   36 0 30  
  11 100   32 0 26  
  0 50   2 1 0  
  56 100   28 0 99  
  56 50   75 0 134  
  13 100   33 0 40  
  13 50   12 0 141  
  43 50   30 0 290  
  36 50       74  
  36 100       91  
  13 50       42  
  13 100       44  
  23 50       150  
  32 100       57  
  32 50       123  
  13 100       64  
  13 50       63  
  19 100       132  
  2         4  
  2         17  
  0         0  
  0         0  
  2         245  
  28         56  
  28         83  
  16         54  
  16         48  
  12         211  
  75         122  
  75         179  
  72         930  
  72         172  
  3         23  
  33         62  
  33         92  
  13         53  
  13         38  
  20         394  
  12         28  
  12         52  
  11         39  
  11         29  
  1         6  
  30         69  
  30         91  
  18         46  
  18         81  
  12         266  
217             sub $_ {
218             my \$s = shift;
219             if (\@_) {
220             \$s->{$_} = shift;
221             return \$s
222             }
223             else {
224             return defined \$s->{$_} ? \$s->{$_} : '';
225             }
226             }
227             END
228             }
229              
230 0     0   0 sub DESTROY {} # To avoid autoloading it.
231              
232             sub module {
233 54     54 1 177 my $s = shift;
234 54 100       130 if (@_) {
235 13         326 $s->found_path('');
236 13         34 $s->{module} = shift;
237 13         494 $s->mod_path(File::Spec->catdir(split '::', $s->{module}) . '.pm');
238 13         359 $s->canon_path(join('/',split('::', $s->{module})).'.pm');
239 13         34 return $s;
240             }
241             else {
242 41         2643 return $s->{module};
243             }
244             }
245              
246             sub condition {
247 13     13 1 40 my $s = shift;
248 13 50       55 if (@_) {
249 13         1044 $s->found_path('');
250 13         327 $s->condition_str(shift);
251 13         89 $s->parse_condition;
252 13         22 return $s;
253             }
254             else {
255 0         0 return $s->condition_str;
256             }
257             }
258              
259             sub versionlib {
260 41     41 1 64 my $s = shift;
261 41 100       104 if (ref $s) {
    50          
262 40 100       99 if (@_) {
263 18         543 $s->found_path('');
264 18         44 $s->{versionlib} = shift;
265 18         859 $s->versionarch(File::Spec->catdir($s->{versionlib},
266             $Config{archname}
267             ));
268 18         51 return $s;
269             }
270             else {
271 22         554 return $s->{versionlib};
272             }
273             }
274             elsif (@_) {
275 1         2 $versionlib = shift;
276             }
277 1         2 return $versionlib;
278             }
279              
280             sub distribution_modules {
281 3     3 0 6 my ($s, $path) = (@_, '');
282 3   100     25 $s->{distribution_modules} ||= {};
283 3 50       887 return $s->{distribution_modules}
284             unless $path;
285 0 0       0 $path =~ s/\.pm$/\.yaml/
286             or return {};
287 0 0       0 open META, $path
288             or return {};
289 0         0 $s->{distribution_modules} = {};
290 0         0 my $meta = do {local $/; };
  0         0  
  0         0  
291 0         0 close META;
292 0         0 $s->{distribution_modules}{$_} = 1 for ($meta =~ /^ - (\S+)/gm);
293 0         0 $s->{distribution_modules}
294             }
295              
296             sub export {
297 1     1 0 1 my ($s) = @_;
298 1 50       19 return if $s->no_export;
299 1         7 $s->module->can('import')
300             }
301              
302             sub get_loaded_version {
303 8     8 0 17 my ($s) = @_;
304 8         186 my $path = $INC{$s->canon_path};
305 8         30 my $version = $s->module->VERSION;
306 8 50 33     222 if ($path =~ s/\.pm$/\.yaml/ and -f $path) {
307 0 0       0 open META, $path
308             or croak "Can't open $path for input:\n$!";
309 0         0 my $meta = do {local $/;};
  0         0  
  0         0  
310 0         0 close META;
311 0 0       0 if ($meta =~ /^install_version\s*:\s*(\S+)$/m) {
312 0         0 $version = $1;
313             }
314             }
315             $version
316 8         71 }
317              
318             sub parse_condition {
319 13     13 0 31 my ($s) = @_;
320 13         290 my @condition = split /\s+/, $s->condition_str;
321 13 50       340 $s->fancy(@condition ? 0 : 1);
322 14         33 @condition = map {
323 13         44 my $v;
324 14 50       113 if (/^(!)?(\d[\d\.]*)?(?:(-)(\d[\d\.]*)?)?$/) {
325 14 100 100     344 $s->fancy(1)
326             if defined($1) or defined($3);
327 14   50     235 my $lower = qv($2 || '0.00');
328 14 50       109 my $upper = defined($4) ? qv($4) :
    100          
329             defined($3) ? '99999999' :
330             $lower;
331 14 100       50 my $negate = defined($1) ? 1 : 0;
332 14 50       373 croak "Lower bound > upper bound in '$_'\n"
333             if $lower > $upper;
334 14         63 $v = [$lower, $upper, $negate];
335             }
336             else {
337 0         0 croak "Invalid condition '$_' specified for 'only'\n";
338             }
339 14         65 $v;
340             } @condition;
341 13         352 $s->condition_spec(\@condition)
342             }
343              
344             sub all_versions {
345 2     2 0 5 my ($s) = @_;
346 2         4 my %versions;
347 2         6 for my $lib ($s->versionlib, $s->versionarch) {
348 4         10 opendir LIB, $s->versionlib;
349 4         83 while (my $dir = readdir(LIB)) {
350 18 100       88 next unless $dir =~ /^\d[\d\.]*$/;
351 10 50       80 next if $dir eq $Config{version};
352 10         53 $versions{$dir} = 1;
353             }
354 4         48 closedir(LIB);
355             }
356 2         19 keys %versions
357             }
358              
359             sub check_version {
360 13     13 0 32 my ($s, $version) = @_;
361 13         41 my @specs = @{$s->condition_spec};
  13         454  
362 13 50       46 return 1 unless @specs;
363 13         23 my $match = 0;
364 13         68 for my $spec (@specs) {
365 17         33 my ($lower, $upper, $negate) = @$spec;
366 17 50 33     84 next if $match and not $negate;
367 17 100 66     348 if ($version >= $lower and $version <= $upper) {
368 1 50       3 return 0 if $negate;
369 1         4 $match = 1;
370             }
371             }
372             $match
373 13         59 }
374              
375             sub module_not_found {
376 14     14   132356 use Data::Dumper;
  14         148307  
  14         8630  
377 10     10 0 22 my ($s) = @_;
378 10         33 my $p = $s->module;
379 10 100       357 if (defined $INC{$s->canon_path}) {
380 6         39 my $v = qv($s->get_loaded_version);
381 6         194 my $req = $s->condition_str();
382 6         8573 croak <
383             Loaded $p, but version ($v) did not satisfy the requirement:
384              
385             use only $p => '$req';
386              
387             END
388             }
389 4         15 my $faux_inc = 'only:' . $s->module . ':' . $s->versionlib;
390 4         74 my $inc = join "\n", map " - $_", ($faux_inc, @INC);
391 4         1120 croak <
392             Can't locate desired version of $p in \@INC:
393             $inc
394             END
395             }
396              
397             sub _install {
398 0     0     require only::install;
399 0           my %args;
400 0 0 0       if (@ARGV == 1 and $ARGV[0] =~ /^[\d\.]+$/) {
401 0           $args{version} = $ARGV[0];
402             }
403             else {
404 0           for (@ARGV) {
405 0 0         unless (/^(\w+)=(\S+)$/) {
406 0           croak "Invalid option format '$_' for only=install\n";
407             }
408 0           $args{$1} = $2;
409             }
410             }
411 0           only::install::install(%args);
412 0           exit 0;
413             }
414            
415             1;
416              
417             __END__