File Coverage

blib/lib/Sub/Versions.pm
Criterion Covered Total %
statement 17 79 21.5
branch 0 30 0.0
condition 0 7 0.0
subroutine 6 9 66.6
pod n/a
total 23 125 18.4


line stmt bran cond sub pod time code
1             package Sub::Versions;
2             # ABSTRACT: Subroutine versioning syntactic sugar
3              
4 1     1   191525 use 5.014;
  1         4  
5 1     1   792 use exact;
  1         45680  
  1         41  
6              
7 1     1   3192 use Sub::Util 'subname';
  1         2  
  1         40  
8 1     1   521 use Devel::Hook;
  1         1065  
  1         469  
9              
10             our $VERSION = '1.06'; # VERSION
11              
12             my $versions;
13             my $subspaces;
14              
15             sub import {
16 0     0     my $package = ( caller() )[0];
17 0           my $mca = \&{"$package\::MODIFY_CODE_ATTRIBUTES"};
  0            
18              
19             _eq_sub( $package . '::MODIFY_CODE_ATTRIBUTES', sub {
20 0     0     my ( $package, $code, @attrs ) = @_;
21 0 0         $mca->(@_) if ( defined &$mca );
22              
23 0   0       my $version = substr( ( grep { /^v\d+$/ } @attrs )[0] || ' ', 1 );
24 0 0         return unless ($version);
25              
26 0           my $name = subname($code);
27 0           my ( $class, $method ) = $name =~ m/^(.+)::(.+?)$/;
28              
29             # store sub ref along with version in $versions for later use
30             $versions->{$package}{$method} = [
31 0           sort { $b->{version} <=> $a->{version} } (
32 0 0         @{ $versions->{$package}{$method} || [] },
  0            
33             {
34             version => $version,
35             code => $code,
36             },
37             )
38             ];
39              
40             # remove existing sub
41             {
42 1     1   6 no strict 'refs';
  1         1  
  1         1540  
  0            
43 0           undef *{$name};
  0            
44             }
45              
46             # setup versioned sub
47 0           _eq_sub( "${name}_v${version}", $code );
48              
49             # setup version interstitial objects
50 0           my $subspace = __PACKAGE__ . "::Subspace::${package}::v${version}";
51 0 0         unless ( defined &{"${package}::v${version}"} ) {
  0            
52             _eq_sub( "${subspace}::new", sub {
53 0           my ( $self, $object ) = @_;
54 0           return bless( { version => $version, object => $object }, $self );
55 0           } );
56             _eq_sub( "${package}::v${version}", sub {
57 0   0       return $subspaces->{"${package}::v${version}"} ||= "$subspace"->new(shift);
58 0           } );
59             }
60             _eq_sub( "${subspace}::$method", sub {
61 0           my $self = shift;
62 0           my $target_method = "${method}_v${version}";
63 0           $self->{object}->$target_method(@_);
64 0           } );
65              
66             # setup most recent version as default sub
67             Devel::Hook->push_INIT_hook( sub {
68 0           for my $method ( keys %{ $versions->{$package} } ) {
  0            
69 0           _eq_sub( $package . '::' . $method, $versions->{$package}{$method}[0]{code} );
70             }
71 0           } );
72              
73             # setup the subver() method functionality
74             _eq_sub( "$package\::subver", sub {
75 0           my ( $self, $version, $method ) = @_;
76 0           ( my $v = $version ) =~ s/\s+//g;
77              
78 0           my ( $v_vector, $v_number ) = $v =~ /^([<>=]{0,2})(\d+)$/;
79 0   0       $v_vector ||= '=';
80 0 0         croak(qq{"$version" not a valid version criteria}) unless ( defined $v_number );
81              
82             # unique version numbers of any method matching the name $method
83             my %versions_found = map {
84 0 0         map { $_->{version} => 1 } @{ $versions->{$_}{$method} || [] }
  0            
  0            
  0            
85             } keys %$versions;
86              
87             # valid version numbers based on version vector input
88 0           my @valid_versions = sort { $b <=> $a } grep {
89 0 0         ( $v_vector eq '=' ) ? $_ == $v_number :
  0 0          
    0          
    0          
    0          
    0          
90             ( $v_vector eq '==' ) ? $_ == $v_number :
91             ( $v_vector eq '>=' ) ? $_ >= $v_number :
92             ( $v_vector eq '<=' ) ? $_ <= $v_number :
93             ( $v_vector eq '>' ) ? $_ > $v_number :
94             ( $v_vector eq '<' ) ? $_ < $v_number : $_ != $v_number
95             } keys %versions_found;
96              
97             # pick the highest version that can be called off the object
98 0           my $selected_version = ( grep { $self->can( $method . '_v' . $_ ) } @valid_versions )[0];
  0            
99              
100 0 0         if ( defined $selected_version ) {
101 0           $selected_version = 'v' . $selected_version;
102 0           return sub { $self->$selected_version->$method(@_) };
  0            
103             }
104              
105 0 0         return sub { $self->$method(@_) } if ( $self->can($method) );
  0            
106              
107 0           croak(qq{No "$method" subroutine with "$version" version});
108 0 0         } ) unless ( defined &{"$package\::subver"} );
  0            
109              
110 0           return;
111 0           } );
112             }
113              
114             sub _eq_sub {
115 0     0     my ( $name, $code ) = @_;
116              
117             {
118 1     1   7 no strict 'refs';
  1         1  
  1         108  
  0            
119 0           *{$name} = $code;
  0            
120             }
121              
122 0           return $code;
123             }
124              
125             1;
126              
127             __END__