File Coverage

blib/lib/ex/monkeypatched.pm
Criterion Covered Total %
statement 50 50 100.0
branch 16 18 88.8
condition 7 12 58.3
subroutine 11 11 100.0
pod 0 1 0.0
total 84 92 91.3


line stmt bran cond sub pod time code
1             package ex::monkeypatched;
2              
3 3     3   39583 use strict;
  3         8  
  3         120  
4 3     3   17 use warnings;
  3         5  
  3         108  
5              
6 3     3   6736 use Sub::Name qw;
  3         3799  
  3         332  
7 3     3   26 use Carp qw;
  3         6  
  3         2371  
8              
9             our $VERSION = '0.03';
10              
11             sub import {
12 10     10   46886 my $invocant = shift;
13 10   66     132 my $norequire = @_ && $_[0] && $_[0] eq '-norequire' && shift;
14 10 100       1776 if (@_) {
15 9 100       33 my @injections = _parse_injections(@_)
16             or croak "Usage: use $invocant \$class => %methods
17             or: use $invocant (class => \$class, methods => \\%methods)
18             or: use $invocant (method => \$name, implementations => \\%impl)";
19 8 100       58 _require(map { $_->[0] } @injections)
  8         26  
20             if !$norequire;
21 6         25 _inject_methods(@injections);
22             }
23             }
24              
25             sub _require {
26 5     5   14 for (@_) {
27 8         305 (my $as_file = $_) =~ s{::|'}{/}g;
28 8         11829 require "$as_file.pm"; # dies if no such file is found
29             }
30             }
31              
32             sub _parse_injections {
33              
34 12 100 66 12   90 if (@_ == 1 && ref $_[0] eq 'HASH') {
    100          
35 2         5 my $opt = shift;
36 2 100 66     55 if (defined $opt->{class} && ref $opt->{methods} eq 'HASH') {
    50 33        
37 2         12 return map { [$opt->{class}, $_, $opt->{methods}{$_}] }
  1         5  
38 1         3 keys %{ $opt->{methods} };
39             }
40             elsif (defined $opt->{method} && ref $opt->{implementations} eq 'HASH') {
41 2         12 return map { [$_, $opt->{method}, $opt->{implementations}{$_}] }
  1         5  
42 1         2 keys %{ $opt->{implementations} };
43             }
44             }
45             elsif (@_ % 2) {
46 9         13 my @injections;
47 9         15 my $target = shift;
48 9         76 push @injections, [$target, splice @_, 0, 2]
49             while @_;
50 9         44 return @injections;
51             }
52              
53 1         30 return;
54             }
55              
56             sub inject {
57 3     3 0 7103 my $invocant = shift;
58 3 50       12 my @injections = _parse_injections(@_)
59             or croak "Usage: $invocant->inject(\$class, %methods)
60             or: $invocant->inject({ class => \$class, methods => \\%methods })
61             or: $invocant->inject({ method => \$name, implementations => \\%impl })";
62 3         10 _inject_methods(@injections);
63             }
64              
65             sub _inject_methods {
66 9     9   28 for (@_) {
67 15         38 my ($target, $name, undef) = @$_;
68 15 100       278 croak qq[Can't monkey-patch: $target already has a method "$name"]
69             if $target->can($name);
70             }
71 6         37 _install_subroutine(@$_) for @_;
72             }
73              
74             sub _install_subroutine {
75 10     10   22 my ($target, $name, $code) = @_;
76 10         24 my $full_name = "$target\::$name";
77 10         668 my $renamed_code = subname($full_name, $code);
78 3     3   19 no strict qw;
  3         5  
  3         271  
79 10         172 *$full_name = $renamed_code;
80             }
81              
82             1;
83             __END__