File Coverage

lib/Sub/Override.pm
Criterion Covered Total %
statement 147 148 99.3
branch 23 24 95.8
condition 11 12 91.6
subroutine 33 33 100.0
pod 6 6 100.0
total 220 223 98.6


line stmt bran cond sub pod time code
1             package Sub::Override;
2              
3 1     1   88710 use strict;
  1         1  
  1         60  
4 1     1   4 use warnings;
  1         3  
  1         41  
5              
6 1     1   3 use Carp qw(croak);
  1         1  
  1         46  
7 1     1   4 use Scalar::Util qw(set_prototype);
  1         1  
  1         90  
8              
9             our $VERSION = '0.12';
10              
11             sub new {
12 7     7 1 206399 my $class = shift;
13 7         17 my $self = bless {}, $class;
14 7 100       49 $self->replace(@_) if @_;
15 7         17 return $self;
16             }
17              
18             {
19 1     1   5 no warnings 'once';
  1         1  
  1         77  
20             # because override() was a better name and this is what it should have been
21             # called.
22             *override = *replace{CODE};
23             }
24              
25             sub replace {
26 11     11 1 2993 my ( $self, $sub_to_replace, $new_sub ) = @_;
27 11         30 $sub_to_replace = $self->_get_fully_qualified_sub_name($sub_to_replace);
28 11         27 $self->_ensure_code_slot_exists($sub_to_replace)->_validate_sub_ref($new_sub);
29             {
30 1     1   3 no strict 'refs';
  1         2  
  1         33  
  9         13  
31 9   100     58 $self->{$sub_to_replace} ||= *$sub_to_replace{CODE};
32 1     1   2 no warnings 'redefine';
  1         1  
  1         79  
33 9         47 *$sub_to_replace = $new_sub;
34             }
35 9         61 return $self;
36             }
37              
38             sub inject {
39 2     2 1 983 my ( $self, $sub_to_inject, $new_sub ) = @_;
40 2         7 $sub_to_inject = $self->_get_fully_qualified_sub_name($sub_to_inject);
41 2         8 $self->_ensure_code_slot_does_not_exist($sub_to_inject)->_validate_sub_ref($new_sub);
42             {
43 1     1   6 no strict 'refs';
  1         1  
  1         60  
  1         2  
44 1         4 $self->{$sub_to_inject} = undef;
45 1     1   7 no warnings 'redefine';
  1         1  
  1         94  
46 1         4 *$sub_to_inject = $new_sub;
47             }
48 1         5 return $self;
49             }
50              
51             sub inherit {
52 4     4 1 2807 my ( $self, $sub_to_inherit, $new_sub ) = @_;
53 4         12 $sub_to_inherit = $self->_get_fully_qualified_sub_name($sub_to_inherit);
54 4         14 $self->_ensure_code_slot_exists_in_parent_class($sub_to_inherit)->_validate_sub_ref($new_sub);
55             {
56 1     1   4 no strict 'refs';
  1         1  
  1         30  
  1         2  
57 1         4 $self->{$sub_to_inherit} = undef;
58 1     1   3 no warnings 'redefine';
  1         1  
  1         73  
59 1         4 *$sub_to_inherit = $new_sub;
60             }
61 1         6 return $self;
62             }
63              
64             sub wrap {
65 2     2 1 966 my ( $self, $sub_to_replace, $new_sub ) = @_;
66 2         5 $sub_to_replace = $self->_get_fully_qualified_sub_name($sub_to_replace);
67 2         9 $self->_ensure_code_slot_exists($sub_to_replace)->_validate_sub_ref($new_sub);
68             {
69 1     1   4 no strict 'refs';
  1         1  
  1         137  
  2         2  
70 2   50     16 $self->{$sub_to_replace} ||= *$sub_to_replace{CODE};
71              
72             # passing $sub_to_replace directly to arguments prevents early destruction.
73 2         35 my $weakened_sub_to_replace = $self->{$sub_to_replace};
74 2     4   11 my $code = sub { unshift(@_, $weakened_sub_to_replace); goto &$new_sub };
  4         2018  
  4         15  
75 2         4 my $prototype = prototype($self->{$sub_to_replace});
76 2 100       14 set_prototype(\&$code, $prototype) if defined $prototype;
77              
78 1     1   6 no warnings 'redefine';
  1         0  
  1         161  
79 2         9 *$sub_to_replace = $code;
80             }
81 2         11 return $self;
82             }
83              
84             sub restore {
85 10     10 1 6982 my ( $self, $name_of_sub ) = @_;
86 10         27 $name_of_sub = $self->_get_fully_qualified_sub_name($name_of_sub);
87 10 100 100     34 if ( !$name_of_sub && 1 == keys %$self ) {
88 1         4 ($name_of_sub) = keys %$self;
89             }
90             croak(
91 10 100       144 sprintf 'You must provide the name of a sub to restore: (%s)' => join
92             ', ' => sort keys %$self )
93             unless $name_of_sub;
94             croak("Cannot restore a sub that was not replaced ($name_of_sub)")
95 9 100       244 unless exists $self->{$name_of_sub};
96              
97 1     1   4 no strict 'refs';
  1         1  
  1         20  
98 1     1   12 no warnings 'redefine';
  1         1  
  1         84  
99 7         15 my $maybe_sub_ref = delete $self->{$name_of_sub};
100 7 100       16 if ( defined $maybe_sub_ref ) {
101 5         38 *$name_of_sub = $maybe_sub_ref;
102             }
103             else {
104 2         11 undef *$name_of_sub;
105             }
106 7         23 return $self;
107             }
108              
109             sub DESTROY {
110 7     7   6476 my $self = shift;
111 1     1   4 no strict 'refs';
  1         1  
  1         25  
112             # "misc" suppresses warning: 'Undefined value assigned to typeglob'
113 1     1   3 no warnings 'redefine', 'misc';
  1         1  
  1         293  
114 7         149 while ( my ( $sub_name, $maybe_sub_ref ) = each %$self ) {
115 4 50       12 if ( defined $maybe_sub_ref ) {
116 4         48 *$sub_name = $maybe_sub_ref;
117             }
118             else {
119 0         0 undef *$sub_name;
120             }
121             }
122             }
123              
124             sub _get_fully_qualified_sub_name {
125 29     29   63 my ( $self, $subname ) = @_;
126 29 100 100     261 if ( ( $subname || '' ) =~ /^\w+$/ ) { # || "" for suppressing test warnings
127 14         22 my $package = do {
128 14         23 my $call_level = 0;
129 14         35 my $this_package;
130 14   100     45 while ( !$this_package || __PACKAGE__ eq $this_package ) {
131 29         120 ($this_package) = caller($call_level);
132 29         127 $call_level++;
133             }
134 14         48 $this_package;
135             };
136 14         36 $subname = "${package}::$subname";
137             }
138 29         62 return $subname;
139             };
140              
141             sub _validate_sub_ref {
142 14     14   26 my ( $self, $sub_ref ) = @_;
143 14 100       41 unless ( 'CODE' eq ref $sub_ref ) {
144 1         120 croak("($sub_ref) must be a code reference");
145             }
146 13         23 return $self;
147             };
148              
149             sub _ensure_code_slot_exists {
150 13     13   27 my ( $self, $code_slot ) = @_;
151 1     1   5 no strict 'refs';
  1         2  
  1         111  
152 13 100       14 unless ( defined *{$code_slot}{CODE} ) {
  13         68  
153 1         189 croak("Cannot replace non-existent sub ($code_slot)");
154             }
155 12         39 return $self;
156             };
157              
158             sub _ensure_code_slot_does_not_exist {
159 6     6   11 my ( $self, $code_slot ) = @_;
160 1     1   5 no strict 'refs';
  1         1  
  1         77  
161 6 100       9 if ( defined *{$code_slot}{CODE} ) {
  6         33  
162 3         465 croak("Cannot create a sub that already exists ($code_slot)");
163             }
164 3         8 return $self;
165             };
166              
167             sub _ensure_code_slot_exists_in_parent_class {
168 4     4   9 my ( $self, $code_slot ) = @_;
169 4         11 $self->_ensure_code_slot_does_not_exist($code_slot);
170             {
171 1     1   4 no strict 'refs';
  1         1  
  1         81  
  2         3  
172 2         5 my $class = *{$code_slot}{PACKAGE};
  2         5  
173 2         3 my $method = *{$code_slot}{NAME};
  2         6  
174 2 100       151 croak("Sub does not exist in parent class ($code_slot)")
175             unless $class->can($method);
176             }
177 1         43 return $self;
178             };
179              
180             1;
181              
182             __END__