File Coverage

blib/lib/Sub/Methodical.pm
Criterion Covered Total %
statement 79 82 96.3
branch 13 16 81.2
condition 8 11 72.7
subroutine 16 16 100.0
pod n/a
total 116 125 92.8


line stmt bran cond sub pod time code
1 2     2   46944 use strict;
  2         6  
  2         79  
2 2     2   12 use warnings;
  2         5  
  2         144  
3              
4             package Sub::Methodical;
5              
6             our $VERSION = '0.002';
7              
8             my %methodical;
9             my %wrapped;
10             my %auto_methodical;
11              
12 2     2   12 use B;
  2         10  
  2         125  
13 2     2   1805 use PadWalker;
  2         2163  
  2         105  
14 2     2   3147 use Filter::EOF;
  2         5947  
  2         13  
15 2     2   2101 use Sub::Install ();
  2         3909  
  2         259  
16             use Sub::Exporter -setup => {
17             exports => [
18             MODIFY_CODE_ATTRIBUTES => \&_build_MODIFY,
19             AUTOLOAD => \&_build_AUTOLOAD,
20             ],
21             groups => {
22             default => [qw(MODIFY_CODE_ATTRIBUTES)],
23             inherit => [qw(AUTOLOAD)],
24             },
25             collectors => {
26             -auto => sub {
27 1         724 my ($col, $arg) = @_;
28 1         4 $auto_methodical{$arg->{into}} = 1;
29 1         2 push @{ $arg->{import_args} }, (
  1         5  
30             [ 'MODIFY_CODE_ATTRIBUTES', undef ],
31             );
32             },
33             },
34 2     2   2449 };
  2         26035  
  2         52  
35              
36             sub _build_MODIFY {
37             Filter::EOF->on_eof_call(sub {
38 3     3   1823 for my $pkg (keys %methodical) {
39 2         4 for my $sub (@{ $methodical{$pkg} }) {
  2         7  
40 2         5 _wrap($pkg, $sub);
41             }
42             }
43 3         244 for my $pkg (keys %auto_methodical) {
44 2     2   1406 no strict 'refs';
  2         6  
  2         1018  
45 2         4 for my $subname (grep {
  64         231  
46 2         16 !/^MODIFY_.+_ATTRIBUTES$/ &&
47             $_ ne 'AUTOLOAD' &&
48             !/^_/ &&
49 70 100 100     419 *{$pkg . '::' . $_}{CODE}
      100        
50             } keys %{$pkg . '::'}) {
51 58         174 my $sub = \&{$pkg . '::' . $subname};
  58         144  
52 58 100       400 next unless B::svref_2object($sub)->STASH->NAME eq $pkg;
53 2         6 _wrap($pkg, $sub);
54             }
55             }
56 3     3   438 });
57             return sub {
58 1     1   2841 my ($pkg, $ref, @attrs) = @_;
59              
60 1 50 33     6 if (ref $ref eq 'CODE' and grep { $_ eq 'Methodical' } @attrs) {
  1         7  
61 1   50     1 push @{ $methodical{$pkg} ||= [] }, $ref;
  1         10  
62 1         2 @attrs = grep { $_ ne 'Methodical' } @attrs;
  1         3  
63             }
64 1         3 return @attrs;
65 3         95 };
66             }
67              
68             sub _build_AUTOLOAD {
69             return sub {
70 2     2   2095 our $AUTOLOAD;
71 2         12 my ($pkg, $method) = $AUTOLOAD =~ /^(.+)::(.+)$/;
72 2 100       7 my ($wrap_pkg) = grep { $pkg->isa($_) && $wrapped{$_}{$method} }
  4         41  
73             keys %wrapped;
74 2 100       6 if ($wrap_pkg) {
75 2     2   13 no strict 'refs';
  2         5  
  2         969  
76 1         3 goto &{$wrap_pkg . '::' . $method};
  1         8  
77             }
78 1         10 require Carp;
79 1         177 Carp::croak "Undefined subroutine &$AUTOLOAD called";
80 2     2   1343 };
81             }
82              
83             sub _wrap {
84 4     4   9 my ($pkg, $sub) = @_;
85 4         23 require B;
86 4         51 my $name = B::svref_2object($sub)->GV->NAME;
87 4         35 (my $as = $name) =~ s/.*:://;
88             #warn "wrapping $name ($pkg\::$as)\n";
89 4         10 $wrapped{$pkg}{$as} = $sub;
90             Sub::Install::reinstall_sub({
91             into => $pkg,
92             as => $as,
93             code => sub {
94 12 100   12   3897 if (eval { $_[0]->isa($pkg) }) {
  12         86  
95             #warn "calling $name directly: @_\n";
96 7         20 return $sub->(@_);
97             }
98 5         35 my $pad = PadWalker::peek_my(1);
99 5         10 my $self = $pad->{'$self'};
100 5 50       13 unless ($self) {
101 0         0 die "can't find \$self!";
102             }
103 5 50       6 unless (eval { $$self->isa($pkg) }) {
  5         40  
104 0         0 require Carp;
105 0         0 Carp::croak sprintf
106             "Methodical '%s' called with incorrect invocant '%s' (wanted '%s')",
107             $as, $$self, $pkg;
108             }
109             #warn "calling $name with self = $$self, @_\n";
110 5         28 $$self->$as(@_);
111             },
112 4         88 });
113             }
114              
115             1;
116             __END__