File Coverage

blib/lib/Sub/Attributes.pm
Criterion Covered Total %
statement 51 83 61.4
branch 8 10 80.0
condition 6 11 54.5
subroutine 10 12 83.3
pod 1 1 100.0
total 76 117 64.9


line stmt bran cond sub pod time code
1             package Sub::Attributes;
2 1     1   446 use strict;
  1         1  
  1         21  
3 1     1   3 use warnings;
  1         0  
  1         19  
4              
5             # voodoo
6 1     1   3 no strict 'refs';
  1         2  
  1         19  
7 1     1   2 no warnings qw(reserved redefine);
  1         1  
  1         27  
8              
9 1     1   3 use B 'svref_2object';
  1         1  
  1         43  
10 1     1   3 use Carp 'croak';
  1         1  
  1         43  
11              
12 1     1   364 BEGIN { our $VERSION = 0.03 }
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 1     1   2 our %attributes = ();
25             our %allowed = (
26             # runtime check that caller is the class
27             Private => sub {
28 1         5 my ($class) = @_;
29             return sub {
30 2         3 my ($coderef, @args) = @_;
31 2         7 my ($package, $filename, $line, $sub) = caller(2);
32 2 100 66     89 croak 'Only the object may call this sub' unless $sub && $sub =~ /^$class\:\:/;
33 1         3 $coderef->(@args);
34 1         4 };
35             },
36             # runtime check that the first arg is the class
37             ClassMethod => sub {
38             return sub {
39 6         9 my ($coderef, @args) = @_;
40             croak 'Class method called as function / object method'
41 6 100 66     130 unless $args[0] && exists $Sub::Attributes::attributes{ $args[0] };
42 5         7 $coderef->(@args);
43 1         4 };
44             },
45             # runtime check that the first arg is the object
46             Method => sub {
47             return sub {
48 4         8 my ($coderef, @args) = @_;
49             croak 'Method called as function'
50 4 100 66     95 unless $args[0] && exists $Sub::Attributes::attributes{ ref $args[0] };
51 3         6 $coderef->(@args);
52 2         7 };
53             },
54             # compile time override, run a coderef before running the subroutine
55             Before => sub {
56 0         0 my ($class, $value, $coderef) = @_;
57              
58             # full name of the sub to override
59 0         0 my $fq_sub = "$class:\:$value";
60              
61 0         0 my $target_coderef = \&{$fq_sub};
  0         0  
62 0         0 *{$fq_sub} = sub {
63 0         0 $coderef->(@_);
64 0         0 $target_coderef->(@_);
65 0         0 };
66              
67             # we didn't change the method with the attribute
68             # so we return undef as we have no runtime changes
69 0         0 return undef;
70             },
71             # compile time override, run a coderef after running the subroutine
72             After => sub {
73 0         0 my ($class, $value, $coderef) = @_;
74              
75             # full name of the sub to override
76 0         0 my $fq_sub = "$class:\:$value";
77              
78 0         0 my $target_coderef = \&{$fq_sub};
  0         0  
79 0         0 *{$fq_sub} = sub {
80 0         0 my @rv = $target_coderef->(@_);
81 0         0 return $coderef->(@_);
82 0         0 };
83              
84             # we didn't change the method with the attribute
85             # so we return undef as we have no runtime changes
86 0         0 return undef;
87             },
88             # compile time override, run a coderef around running the subroutine
89             Around => sub {
90 0         0 my ($class, $value, $coderef) = @_;
91              
92             # full name of the sub to override
93 0         0 my $fq_sub = "$class:\:\$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         240 );
105             }
106              
107             # this is the registrar for subroutine attributes called at compile time
108             sub MODIFY_CODE_ATTRIBUTES {
109 4     4   1758 my ($class, $coderef, @attributes, @disallowed) = @_;
110              
111 4         11 my $obj = svref_2object($coderef);
112 4         12 my $subroutine = $obj->GV->NAME;
113              
114 4         17 for my $attribute (@attributes) {
115             # parse the attribute into name and value
116 4         23 my ($name, $value) = $attribute =~ qr/^ (\w+) (?:\((\S+?)\))? $/x;
117 4         6 my $overrider = $Sub::Attributes::allowed{$name};
118              
119             # attribute not known, compile error
120 4 50 0     9 push(@disallowed, $name) && next unless $overrider;
121              
122             # make compile time changes, skip ahead if no runtime changes
123 4         6 my $override_coderef = $overrider->($class, $value, $coderef);
124 4 50       5 next unless $override_coderef;
125              
126             # override subroutine with attribute coderef
127 4         4 my $old_coderef = $coderef;
128 4     12   7 $coderef = sub { $override_coderef->($old_coderef, @_) };
  12         2892  
129 4         3 *{"$class:\:$subroutine"} = $coderef;
  4         14  
130             }
131              
132 4         6 $Sub::Attributes::attributes{$class}{ $subroutine } = \@attributes;
133 4         9 return @disallowed;
134             };
135              
136             # this is to support attributes::get from attributes.pm
137             sub FETCH_CODE_ATTRIBUTES {
138 0     0     my ($class, $coderef) = @_;
139 0           my $cv = svref_2object($coderef);
140 0           return @{$Sub::Attributes::attributes{$class}{ $cv->GV->NAME }};
  0            
141             }
142              
143             sub sub_attributes {
144 0     0 1   my ($class) = @_;
145 0           return $Sub::Attributes::attributes{ ref $class };
146             }
147             1;
148             __END__