File Coverage

lib/Spoon/Hooks.pm
Criterion Covered Total %
statement 87 94 92.5
branch 23 32 71.8
condition 4 8 50.0
subroutine 20 21 95.2
pod 0 4 0.0
total 134 159 84.2


line stmt bran cond sub pod time code
1             package Spoon::Hooks;
2 3     3   1871 use Spoon::Base -Base;
  3         7  
  3         37  
3 3     3   5499  
  3     3   7  
  3         96  
  3         15  
  3         6  
  3         441  
4             const hook_class => 'Spoon::Hook';
5             const hooked_class => 'Spoon::Hooked';
6              
7 6     6 0 35 sub add {
8 6         16 my ($target, %hooks) = @_;
9 6         17 my $original = $self->assert_method($target);
10 6         23 my $pre = $self->assert_method($hooks{pre});
11 6         18 my $post = $self->assert_method($hooks{post});
12 6         17 my $replacement = $self->new_hook_sub($original, $pre, $post);
13 6         13 my $hook_point = $self->get_full_name($target);
14 3     3   15 no warnings 'redefine';
  3         5  
  3         135  
15 3     3   15 no strict 'refs';
  3         4  
  3         1391  
16 6         19 *$hook_point = $replacement;
17              
18 6         23 return $self->hooked_class->new(
19             hook_point => $hook_point,
20             original => $original,
21             replacement => $replacement,
22             );
23             }
24              
25 6     6 0 9 sub new_hook_sub {
26 6         11 my ($original, $pre, $post) = @_;
27             sub {
28 12     12   263 my $hook = $self->hook_class->new(
29             code => $original,
30             pre => $pre,
31             post => $post,
32             );
33 12 100       1151 $hook->returned([$hook->pre->(@_, $hook)])
34             if $pre;
35 12 100       256 my $code = $hook->code
36             or return $hook->returned;
37 11         282 my $new_args = $hook->new_args;
38 11 50       55 @_ = @$new_args
39             if $new_args;
40 11         32 $hook->returned([&$code(@_)]);
41 11 100       262 return $hook->post->(@_, $hook)
42             if $hook->post;
43 2         12 return $hook->returned;
44             }
45 6         33 }
46              
47 18     18 0 23 sub assert_method {
48             return shift
49 18 100 100     128 if not defined($_[0]) or ref($_[0]);
50 10         19 my $full_name = $self->get_full_name(shift);
51 10 50       57 my ($package, $method) = ($full_name) =~ /(.*)::(.*)/
52             or die "Can't hook invalid fully qualified method name: '$full_name'";
53 10 50       46 unless ($package->can('new')) {
54 0         0 eval "require $package";
55 0         0 undef($@);
56 0 0       0 die "Can't hook $full_name. Can't find package '$package'"
57             unless $package->can('new');
58             }
59 10         80 my $sub = $full_name;
60 10 100       48 return \&$sub if defined &$sub;
61 3     3   16 no strict 'refs';
  3         8  
  3         613  
62 1     3   86 *$sub = eval <
  3         78  
  3         15  
63             sub {
64             package $package;
65             my \$self = shift;
66             \$self->SUPER::$method(\@_);
67             };
68             END
69 1         5 return \&$sub;
70             }
71              
72 16     16 0 21 sub get_full_name {
73 16         20 my $name = shift;
74 16 100       39 return $name if $name =~ /::/;
75 13 50       53 if ($name =~ /(.*):(.*)/) {
76 13         31 my ($class_id, $method) = ($1, $2);
77 13         44 my $package = $self->hub->registry->lookup->classes->{$class_id};
78 13         702 return $package . '::' . $method;
79             }
80 0         0 return '';
81             }
82              
83             package Spoon::Hooked;
84 3     3   16 use Spoon::Base -Base;
  3         6  
  3         34  
85              
86             field 'hook_point';
87             field 'original';
88             field 'replacement';
89              
90 16     16   1136 sub unhook {
91 16         38 my ($hook_point, $original, $replacement) =
92 16         23 @{$self}{qw(hook_point original replacement)};
93 16         28 %$self = ();
94 16 100       164 return unless defined $hook_point;
95 3     3   1495 no strict 'refs';
  3         7  
  3         194  
96 6         16 my $current = *$hook_point{CODE};
97              
98 6 50       23 die "Unhooking error for $hook_point"
99             unless "$current" eq "$replacement";
100 3     3   16 no warnings;
  3         6  
  3         292  
101 6         16 *$hook_point = $original;
102 6         50 return 1;
103             }
104              
105 6     6   7 sub DESTROY {
106 6         13 $self->unhook;
107             }
108              
109             package Spoon::Hook;
110 3     3   14 use Spoon::Base -Base;
  3         12  
  3         25  
111              
112             field 'code';
113             field 'pre';
114             field 'post';
115             field 'new_args';
116              
117 17     17   152 sub returned {
118 17 100       47 $self->{returned} = shift if @_;
119 17   50     36 $self->{returned} ||= [];
120 17 100       49 wantarray ? (@{$self->{returned}}) : $self->{returned}[0];
  1         7  
121             }
122              
123 0     0   0 sub returned_true {
124 0 0 0     0 @{$self->{returned}} && $self->{returned}[0] && 1;
  0         0  
125             }
126              
127 1     1   11 sub cancel {
128 1         22 $self->code(undef);
129 1         6 return ();
130             }
131              
132             __END__