File Coverage

blib/lib/Module/Implementation.pm
Criterion Covered Total %
statement 79 81 97.5
branch 17 24 70.8
condition 1 3 33.3
subroutine 16 16 100.0
pod 2 2 100.0
total 115 126 91.2


line stmt bran cond sub pod time code
1             package Module::Implementation;
2             # git description: v0.08-2-gd599347
3             $Module::Implementation::VERSION = '0.09';
4              
5 7     7   234036 use strict;
  7         17  
  7         224  
6 7     7   36 use warnings;
  7         14  
  7         225  
7              
8 7     7   6052 use Module::Runtime 0.012 qw( require_module );
  7         13494  
  7         48  
9 7     7   4804 use Try::Tiny;
  7         18205  
  7         5731  
10              
11             # This is needed for the benefit of Test::CleanNamespaces, which in turn loads
12             # Package::Stash, which in turn loads this module and expects a minimum
13             # version.
14             unless ( exists $Module::Implementation::{VERSION}
15             && ${ $Module::Implementation::{VERSION} } ) {
16              
17             $Module::Implementation::{VERSION} = \42;
18             }
19              
20             my %Implementation;
21              
22             sub build_loader_sub {
23 7     7 1 99 my $caller = caller();
24              
25 7         38 return _build_loader( $caller, @_ );
26             }
27              
28             sub _build_loader {
29 7     7   16 my $package = shift;
30 7         38 my %args = @_;
31              
32 7         15 my @implementations = @{ $args{implementations} };
  7         27  
33 7 50       17 my @symbols = @{ $args{symbols} || [] };
  7         58  
34              
35 7         16 my $implementation;
36 7         23 my $env_var = uc $package;
37 7         22 $env_var =~ s/::/_/g;
38 7         16 $env_var .= '_IMPLEMENTATION';
39              
40             return sub {
41 7     7   228 my ( $implementation, $loaded ) = _load_implementation(
42             $package,
43             $ENV{$env_var},
44             \@implementations,
45             );
46              
47 5         23 $Implementation{$package} = $implementation;
48              
49 5         26 _copy_symbols( $loaded, $package, \@symbols );
50              
51 5         33 return $loaded;
52 7         71 };
53             }
54              
55             sub implementation_for {
56 2     2 1 4061 my $package = shift;
57              
58 2         10 return $Implementation{$package};
59             }
60              
61             sub _load_implementation {
62 7     7   18 my $package = shift;
63 7         20 my $env_value = shift;
64 7         14 my $implementations = shift;
65              
66 7 100       31 if ($env_value) {
67 4         14 die "$env_value is not a valid implementation for $package"
68 2 50       3 unless grep { $_ eq $env_value } @{$implementations};
  2         6  
69              
70 2         6 my $requested = "${package}::$env_value";
71              
72             # Values from the %ENV hash are tainted. We know it's safe to untaint
73             # this value because the value was one of our known implementations.
74 2         16 ($requested) = $requested =~ /^(.+)$/;
75              
76             try {
77 2     2   82 require_module($requested);
78             }
79             catch {
80 1     1   832 require Carp;
81 1         192 Carp::croak("Could not load $requested: $_");
82 2         19 };
83              
84 1         722 return ( $env_value, $requested );
85             }
86             else {
87 5         9 my $err;
88 5         11 for my $possible ( @{$implementations} ) {
  5         16  
89 7         22 my $try = "${package}::$possible";
90              
91 7         12 my $ok;
92             try {
93 7     7   282 require_module($try);
94 4         2646 $ok = 1;
95             }
96             catch {
97 3 50   3   2186 $err .= $_ if defined $_;
98 7         64 };
99              
100 7 100       221 return ( $possible, $try ) if $ok;
101             }
102              
103 1         9 require Carp;
104 1 50 33     9 if ( defined $err && length $err ) {
105 1         190 Carp::croak(
106             "Could not find a suitable $package implementation: $err");
107             }
108             else {
109 0         0 Carp::croak(
110             'Module::Runtime failed to load a module but did not throw a real error. This should never happen. Something is very broken'
111             );
112             }
113             }
114             }
115              
116             sub _copy_symbols {
117 5     5   11 my $from_package = shift;
118 5         10 my $to_package = shift;
119 5         11 my $symbols = shift;
120              
121 5         11 for my $sym ( @{$symbols} ) {
  5         16  
122 9 100       67 my $type = $sym =~ s/^([\$\@\%\&\*])// ? $1 : '&';
123              
124 9         76 my $from = "${from_package}::$sym";
125 9         59 my $to = "${to_package}::$sym";
126              
127             {
128 7     7   56 no strict 'refs';
  7         21  
  7         245  
  9         11  
129 7     7   36 no warnings 'once';
  7         18  
  7         1084  
130              
131             # Copied from Exporter
132 9         45 *{$to}
  6         18  
133 1         3 = $type eq '&' ? \&{$from}
134 1         3 : $type eq '$' ? \${$from}
135 1         4 : $type eq '@' ? \@{$from}
136 0           : $type eq '%' ? \%{$from}
137 9 0       34 : $type eq '*' ? *{$from}
    50          
    100          
    100          
    100          
138             : die
139             "Can't copy symbol from $from_package to $to_package: $type$sym";
140             }
141             }
142             }
143              
144             1;
145              
146             # ABSTRACT: Loads one of several alternate underlying implementations for a module
147              
148             __END__