File Coverage

blib/lib/Sub/Attributes.pm
Criterion Covered Total %
statement 63 80 78.7
branch 10 12 83.3
condition 7 14 50.0
subroutine 12 12 100.0
pod 1 1 100.0
total 93 119 78.1


line stmt bran cond sub pod time code
1             package Sub::Attributes;
2 1     1   394 use strict;
  1         1  
  1         20  
3 1     1   3 use warnings;
  1         1  
  1         18  
4              
5             # voodoo
6 1     1   3 no strict 'refs';
  1         2  
  1         22  
7 1     1   3 no warnings qw(reserved redefine);
  1         1  
  1         27  
8              
9 1     1   3 use B 'svref_2object';
  1         1  
  1         47  
10 1     1   2 use Carp 'croak';
  1         1  
  1         45  
11              
12 1     1   393 BEGIN { our $VERSION = 0.04 }
13              
14             # these data structures are key to this module. They're created in a BEGIN block
15             # as package variables so they're available when MODIFY_CODE_ATTRIBUTES is
16             # called:
17             #
18             # %attributes is a hash of subroutine names and their attributes
19             # %allowed is a hash of recognized subroutine attributes with a coderef for
20             # the behavior
21             #
22             # You can relace/extend %allowed in an inherited class to provide your own behavior!
23             BEGIN {
24             our %allowed = (
25             # runtime check that caller is the package
26             Private => sub {
27 1         2 my ($package) = @_;
28             return sub {
29 2         3 my ($coderef, @args) = @_;
30 2         9 my ($package_caller, $filename, $line, $sub) = caller(2);
31 2 100 66     84 croak 'Only the object may call this sub' unless $sub && $sub =~ /^$package\:\:/;
32 1         2 $coderef->(@args);
33 1         5 };
34             },
35             # runtime check that the first arg is the package
36             ClassMethod => sub {
37             return sub {
38 11         14 my ($coderef, @args) = @_;
39             croak 'Class method called as function / object method'
40 11 100 66     162 unless $args[0] && exists $Sub::Attributes::attributes{ $args[0] };
41 10         14 $coderef->(@args);
42 1         6 };
43             },
44             # runtime check that the first arg is the object
45             Method => sub {
46             return sub {
47 7         9 my ($coderef, @args) = @_;
48             croak 'Method called as function'
49 7 100 66     108 unless $args[0] && exists $Sub::Attributes::attributes{ ref $args[0] };
50 6         10 $coderef->(@args);
51 3         15 };
52             },
53             # compile time override, run a coderef before running the subroutine
54             Before => sub {
55 0         0 my ($package, $value, $coderef) = @_;
56              
57             # full name of the sub to override
58 0         0 my $fq_sub = "$package:\:$value";
59              
60 0         0 my $target_coderef = \&{$fq_sub};
  0         0  
61 0         0 *{$fq_sub} = sub {
62 0         0 $coderef->(@_);
63 0         0 $target_coderef->(@_);
64 0         0 };
65              
66             # we didn't change the method with the attribute
67             # so we return undef as we have no runtime changes
68 0         0 return undef;
69             },
70             # compile time override, run a coderef after running the subroutine
71             After => sub {
72 1         3 my ($package, $value, $coderef) = @_;
73              
74             # full name of the sub to override
75 1         8 my $fq_sub = "$package:\:$value";
76              
77 1         2 my $target_coderef = \&{$fq_sub};
  1         3  
78 1         3 *{$fq_sub} = sub {
79 6     6   436 my @rv = $target_coderef->(@_);
80 5         24 $coderef->(@_);
81 5 50       23 return wantarray ? @rv : $rv[0];
82 1         4 };
83              
84             # we didn't change the method with the attribute
85             # so we return undef as we have no runtime changes
86 1         14 return undef;
87             },
88             # compile time override, run a coderef around running the subroutine
89             Around => sub {
90 0         0 my ($package, $value, $coderef) = @_;
91              
92             # full name of the sub to override
93 0         0 my $fq_sub = "$package:\:\$value";
94              
95 0         0 my $target_coderef = \&{$fq_sub};
  0         0  
96 0         0 *{$fq_sub} = sub {
97 0         0 $coderef->($target_coderef, @_);
98 0         0 };
99              
100             # we didn't change the method with the attribute
101             # so we return undef as we have no runtime changes
102 0         0 return undef;
103             },
104 1     1   291 );
105             }
106              
107             # this is the registrar for subroutine attributes called at compile time
108             sub MODIFY_CODE_ATTRIBUTES {
109 5     5   2998 my ($package, $coderef, @attributes, @disallowed) = @_;
110              
111 5         15 my $obj = svref_2object($coderef);
112 5         25 my $subroutine = $obj->GV->NAME;
113              
114 5         27 for my $attribute (@attributes) {
115             # parse the attribute into name and value
116 6         70 my ($name, $value) = $attribute =~ qr/^ (\w+) (?:\((\S+?)\))? $/x;
117 6         14 my $overrider = $Sub::Attributes::allowed{$name};
118              
119             # attribute not known, compile error
120 6 50 0     16 push(@disallowed, $name) && next unless $overrider;
121              
122             # make compile time changes, skip ahead if no runtime changes
123 6         13 my $override_coderef = $overrider->($package, $value, $coderef);
124 6 100       13 next unless $override_coderef;
125              
126             # override subroutine with attribute coderef
127 5         6 my $old_coderef = $coderef;
128 5     20   21 $coderef = sub { $override_coderef->($old_coderef, @_) };
  20         3118  
129 5         5 *{"$package:\:$subroutine"} = $coderef;
  5         25  
130             }
131              
132 5         11 $Sub::Attributes::attributes{$package}{ $subroutine } = \@attributes;
133 5         17 return @disallowed;
134             };
135              
136             sub sub_attributes {
137 1     1 1 766 my ($package) = @_;
138 1   33     6 my $package_name = ref $package || $package;
139 1         4 return $Sub::Attributes::attributes{ $package_name };
140             }
141             1;
142             __END__