File Coverage

blib/lib/Attribute/Handlers.pm
Criterion Covered Total %
statement 343 353 97.1
branch 62 100 62.0
condition 22 40 55.0
subroutine 95 96 98.9
pod 1 2 50.0
total 523 591 88.4


line stmt bran cond sub pod time code
1             package Attribute::Handlers;
2 4     4   71763 use 5.006;
  4         13  
  4         142  
3 4     4   20 use Carp;
  4         7  
  4         342  
4 4     4   21 use warnings;
  4         10  
  4         115  
5 4     4   19 use strict;
  4         5  
  4         158  
6 4     4   19 use vars qw($VERSION $AUTOLOAD);
  4         6  
  4         506  
7             $VERSION = '0.96'; # remember to update version in POD!
8             # $DB::single=1;
9              
10             my %symcache;
11             sub findsym {
12 74     74 1 113 my ($pkg, $ref, $type) = @_;
13 74 100       274 return $symcache{$pkg,$ref} if $symcache{$pkg,$ref};
14 73   33     286 $type ||= ref($ref);
15 4     4   24 no strict 'refs';
  4         6  
  4         164  
16 73         81 foreach my $sym ( values %{$pkg."::"} ) {
  73         842  
17 4     4   19 use strict;
  4         7  
  4         3450  
18 4984 50       10487 next unless ref ( \$sym ) eq 'GLOB';
19 4984         18008 return $symcache{$pkg,$ref} = \$sym
20 4984 100 100     4764 if *{$sym}{$type} && *{$sym}{$type} == $ref;
  2592         13736  
21             }
22             }
23              
24             my %validtype = (
25             VAR => [qw[SCALAR ARRAY HASH]],
26             ANY => [qw[SCALAR ARRAY HASH CODE]],
27             "" => [qw[SCALAR ARRAY HASH CODE]],
28             SCALAR => [qw[SCALAR]],
29             ARRAY => [qw[ARRAY]],
30             HASH => [qw[HASH]],
31             CODE => [qw[CODE]],
32             );
33             my %lastattr;
34             my @declarations;
35             my %raw;
36             my %phase;
37             my %sigil = (SCALAR=>'$', ARRAY=>'@', HASH=>'%');
38             my $global_phase = 0;
39             my %global_phases = (
40             BEGIN => 0,
41             CHECK => 1,
42             INIT => 2,
43             END => 3,
44             );
45             my @global_phases = qw(BEGIN CHECK INIT END);
46              
47             sub _usage_AH_ {
48 0     0   0 croak "Usage: use $_[0] autotie => {AttrName => TieClassName,...}";
49             }
50              
51             my $qual_id = qr/^[_a-z]\w*(::[_a-z]\w*)*$/i;
52              
53             sub import {
54 5     5   695 my $class = shift @_;
55 5 50       19 return unless $class eq "Attribute::Handlers";
56 5         504 while (@_) {
57 1         2 my $cmd = shift;
58 1 50       8 if ($cmd =~ /^autotie((?:ref)?)$/) {
59 1 50       13 my $tiedata = ($1 ? '$ref, ' : '') . '@$data';
60 1         2 my $mapping = shift;
61 1 50       5 _usage_AH_ $class unless ref($mapping) eq 'HASH';
62 1         6 while (my($attr, $tieclass) = each %$mapping) {
63 3         20 $tieclass =~ s/^([_a-z]\w*(::[_a-z]\w*)*)(.*)/$1/is;
64 3   50     18 my $args = $3||'()';
65 3 50 33 1   241 _usage_AH_ $class unless $attr =~ $qual_id
  1   33 1   5  
  1     1   1  
  1         27  
  1         41  
  1         7  
  1         10  
  1         3  
  1         48  
  1         5  
66             && $tieclass =~ $qual_id
67             && eval "use base q\0$tieclass\0; 1";
68 3 50       33 if ($tieclass->isa('Exporter')) {
69 0         0 local $Exporter::ExportLevel = 2;
70 0         0 $tieclass->import(eval $args);
71             }
72 3         6 $attr =~ s/__CALLER__/caller(1)/e;
  0         0  
73 3 100       13 $attr = caller()."::".$attr unless $attr =~ /::/;
74 3 50 33 1 0 652 eval qq{
  1 50 33 1   4  
  1 0 33 1   2  
  1 0       36  
  1 50       5  
  1 50       2  
  1 0       28  
  1 50       6  
  1 50       1  
  1 50       38  
  1 50       9  
  1 50       1  
  1 50       42  
  1         5  
  1         2  
  1         34  
  1         6  
  1         2  
  1         31  
  1         4  
  1         2  
  1         55  
  1         6  
  1         2  
  1         37  
75             sub $attr : ATTR(VAR) {
76             my (\$ref, \$data) = \@_[2,4];
77             my \$was_arrayref = ref \$data eq 'ARRAY';
78             \$data = [ \$data ] unless \$was_arrayref;
79             my \$type = ref(\$ref)||"value (".(\$ref||"").")";
80             (\$type eq 'SCALAR')? tie \$\$ref,'$tieclass',$tiedata
81             :(\$type eq 'ARRAY') ? tie \@\$ref,'$tieclass',$tiedata
82             :(\$type eq 'HASH') ? tie \%\$ref,'$tieclass',$tiedata
83             : die "Can't autotie a \$type\n"
84             } 1
85             } or die "Internal error: $@";
86             }
87             }
88             else {
89 0         0 croak "Can't understand $_";
90             }
91             }
92             }
93              
94             # On older perls, code attribute handlers run before the sub gets placed
95             # in its package. Since the :ATTR handlers need to know the name of the
96             # sub they're applied to, the name lookup (via findsym) needs to be
97             # delayed: we do it immediately before we might need to find attribute
98             # handlers from their name. However, on newer perls (which fix some
99             # problems relating to attribute application), a sub gets placed in its
100             # package before its attributes are processed. In this case, the
101             # delayed name lookup might be too late, because the sub we're looking
102             # for might have already been replaced. So we need to detect which way
103             # round this perl does things, and time the name lookup accordingly.
104             BEGIN {
105 4     4   923 my $delayed;
106             sub Attribute::Handlers::_TEST_::MODIFY_CODE_ATTRIBUTES {
107             $delayed = \&Attribute::Handlers::_TEST_::t != $_[1];
108             return ();
109             }
110 4     4   3951 sub Attribute::Handlers::_TEST_::t :T { }
  4         5954  
  4         23  
111 4     102   52 *_delayed_name_resolution = sub() { $delayed };
  101         339  
112 4         26 undef &Attribute::Handlers::_TEST_::MODIFY_CODE_ATTRIBUTES;
113 4         548 undef &Attribute::Handlers::_TEST_::t;
114             }
115              
116             sub _resolve_lastattr {
117 21 50   22   65 return unless $lastattr{ref};
118 21 50       61 my $sym = findsym @lastattr{'pkg','ref'}
119             or die "Internal error: $lastattr{pkg} symbol went missing";
120 21         57 my $name = *{$sym}{NAME};
  21         48  
121 21 50 66     74 warn "Declaration of $name attribute in package $lastattr{pkg} may clash with future reserved word\n"
122             if $^W and $name !~ /[A-Z]/;
123 21         25 foreach ( @{$validtype{$lastattr{type}}} ) {
  21         61  
124 4     4   26 no strict 'refs';
  4         10  
  4         1954  
125 47         64 *{"$lastattr{pkg}::_ATTR_${_}_${name}"} = $lastattr{ref};
  47         308  
126             }
127 21         77 %lastattr = ();
128             }
129              
130             sub AUTOLOAD {
131 0 0   1   0 return if $AUTOLOAD =~ /::DESTROY$/;
132 0         0 my ($class) = $AUTOLOAD =~ m/(.*)::/g;
133 0 0       0 $AUTOLOAD =~ m/_ATTR_(.*?)_(.*)/ or
134             croak "Can't locate class method '$AUTOLOAD' via package '$class'";
135 0         0 croak "Attribute handler '$2' doesn't handle $1 attributes";
136             }
137              
138             my $builtin = qr/lvalue|method|locked|unique|shared/;
139              
140             sub _gen_handler_AH_() {
141             return sub {
142 76 50   76   19165 _resolve_lastattr if _delayed_name_resolution;
143 76         179 my ($pkg, $ref, @attrs) = @_;
144 76         285 my (undef, $filename, $linenum) = caller 2;
145 76         148 foreach (@attrs) {
146 77 50       541 my ($attr, $data) = /^([a-z_]\w*)(?:[(](.*)[)])?$/is or next;
147 77 100       178 if ($attr eq 'ATTR') {
148 4     4   25 no strict 'refs';
  4         7  
  4         2362  
149 21   100     52 $data ||= "ANY";
150 21         77 $raw{$ref} = $data =~ s/\s*,?\s*RAWDATA\s*,?\s*//;
151 21 100       61 $phase{$ref}{BEGIN} = 1
152             if $data =~ s/\s*,?\s*(BEGIN)\s*,?\s*//;
153 21 100       58 $phase{$ref}{INIT} = 1
154             if $data =~ s/\s*,?\s*(INIT)\s*,?\s*//;
155 21 100       55 $phase{$ref}{END} = 1
156             if $data =~ s/\s*,?\s*(END)\s*,?\s*//;
157 20         144 $phase{$ref}{CHECK} = 1
158             if $data =~ s/\s*,?\s*(CHECK)\s*,?\s*//
159 21 100 100     66 || ! keys %{$phase{$ref}};
160             # Added for cleanup to not pollute next call.
161 21 50       54 (%lastattr = ()),
162             croak "Can't have two ATTR specifiers on one subroutine"
163             if keys %lastattr;
164 21 50       56 croak "Bad attribute type: ATTR($data)"
165             unless $validtype{$data};
166 21         77 %lastattr=(pkg=>$pkg,ref=>$ref,type=>$data);
167 21 50       37 _resolve_lastattr unless _delayed_name_resolution;
168             }
169             else {
170 56         88 my $type = ref $ref;
171 56         415 my $handler = $pkg->can("_ATTR_${type}_${attr}");
172 56 100       137 next unless $handler;
173 54         258 my $decl = [$pkg, $ref, $attr, $data,
174             $raw{$handler}, $phase{$handler}, $filename, $linenum];
175 54         86 foreach my $gphase (@global_phases) {
176 216 100       633 _apply_handler_AH_($decl,$gphase)
177             if $global_phases{$gphase} <= $global_phase;
178             }
179 54 100       120 if ($global_phase != 0) {
180             # if _gen_handler_AH_ is being called after
181             # CHECK it's for a lexical, so make sure
182             # it didn't want to run anything later
183            
184 35         53 local $Carp::CarpLevel = 2;
185 35 100       563 carp "Won't be able to apply END handler"
186             if $phase{$handler}{END};
187             }
188             else {
189 19         48 push @declarations, $decl
190             }
191             }
192 74         200 $_ = undef;
193             }
194 75 100       137 return grep {defined && !/$builtin/} @attrs;
  76         430  
195             }
196 16     16   79 }
197              
198             {
199 4     4   23 no strict 'refs';
  4         6  
  4         802  
200             *{"Attribute::Handlers::UNIVERSAL::MODIFY_${_}_ATTRIBUTES"} =
201             _gen_handler_AH_ foreach @{$validtype{ANY}};
202             }
203             push @UNIVERSAL::ISA, 'Attribute::Handlers::UNIVERSAL'
204             unless grep /^Attribute::Handlers::UNIVERSAL$/, @UNIVERSAL::ISA;
205              
206             sub _apply_handler_AH_ {
207 181     181   289 my ($declaration, $phase) = @_;
208 181         506 my ($pkg, $ref, $attr, $data, $raw, $handlerphase, $filename, $linenum) = @$declaration;
209 181 100       2029 return unless $handlerphase->{$phase};
210             # print STDERR "Handling $attr on $ref in $phase with [$data]\n";
211 53         83 my $type = ref $ref;
212 53         109 my $handler = "_ATTR_${type}_${attr}";
213 53         102 my $sym = findsym($pkg, $ref);
214 53 50 66     256 $sym ||= $type eq 'CODE' ? 'ANON' : 'LEXICAL';
215 4     4   41 no warnings;
  4         8  
  4         664  
216 53 100 66     216 if (!$raw && defined($data)) {
217 45 50       81 if ($data ne '') {
218 45     4   3514 my $evaled = eval("package $pkg; no warnings; no strict;
  4     4   30  
  4     4   6  
  4     4   715  
  4     3   21  
  4     3   6  
  4     2   175  
  4     2   26  
  4     2   17  
  4     2   123  
  4     2   19  
  4     2   100  
  4     2   187  
  3     2   12  
  3     2   487  
  3     2   67  
  3     1   12  
  3     1   8  
  3     1   102  
  3     1   15  
  3     1   10  
  3     1   72  
  3     1   24  
  3     1   11  
  3     1   115  
  3     1   519  
  3     1   10  
  3     1   54  
  3     1   13  
  3     1   5  
  3     1   128  
  3     1   15  
  3     1   8  
  3     1   64  
  2     1   9  
  2     1   4  
  2     1   121  
  2     1   14  
  2     1   4  
  2     1   64  
  2     1   10  
  2     1   4  
  2     1   123  
  2     1   13  
  2     1   4  
  2     1   84  
  2     1   10  
  2     1   10  
  2     1   91  
  1     1   6  
  1     1   2  
  1     1   38  
  1     1   6  
  1     1   1  
  1     1   25  
  1     1   5  
  1     1   2  
  1     1   49  
  1     1   7  
  1     1   1  
  1     1   28  
  1     1   5  
  1     1   2  
  1     1   42  
  1         4  
  1         3  
  1         22  
  1         4  
  1         1  
  1         35  
  1         7  
  1         3  
  1         36  
  1         6  
  1         2  
  1         50  
  1         7  
  1         2  
  1         49  
  1         6  
  1         3  
  1         50  
  1         7  
  1         2  
  1         32  
  1         5  
  1         2  
  1         55  
  1         6  
  1         15  
  1         30  
  1         5  
  1         3  
  1         51  
  1         7  
  1         3  
  1         33  
  1         6  
  1         1  
  1         56  
  1         6  
  1         2  
  1         36  
  1         5  
  1         2  
  1         51  
  1         7  
  1         2  
  1         44  
  1         5  
  1         2  
  1         53  
  1         6  
  1         2  
  1         35  
  1         6  
  1         3  
  1         48  
  1         7  
  1         3  
  1         36  
  1         6  
  1         2  
  1         47  
  1         7  
  1         2  
  1         37  
  1         6  
  1         2  
  1         44  
  1         6  
  1         2  
  1         34  
  1         5  
  1         9  
  1         47  
  1         7  
  1         3  
  1         35  
  1         6  
  1         1  
  1         47  
  1         6  
  1         2  
  1         28  
  1         5  
  1         3  
  1         51  
  1         7  
  1         3  
  1         30  
  1         6  
  1         2  
  1         88  
  1         7  
  1         2  
  1         36  
  1         33  
  1         2  
  1         51  
  1         7  
  1         2  
  1         41  
  1         6  
  1         2  
  1         61  
  1         8  
  1         2  
  1         35  
  1         5  
  1         2  
  1         65  
  1         6  
  1         1  
  1         35  
  1         5  
  1         2  
  1         50  
219             local \$SIG{__WARN__}=sub{die}; [$data]");
220 45 100       171 $data = $evaled unless $@;
221             }
222 0         0 else { $data = undef }
223             }
224             $pkg->$handler($sym,
225 53 100 33     342 (ref $sym eq 'GLOB' ? *{$sym}{ref $ref}||$ref : $ref),
226             $attr,
227             $data,
228             $phase,
229             $filename,
230             $linenum,
231             );
232 53         30857 return 1;
233             }
234              
235             {
236 4     4   79 no warnings 'void';
  4         8  
  4         877  
237             CHECK {
238 4     4   3140 $global_phase++;
239 4 50       27 _resolve_lastattr if _delayed_name_resolution;
240 4         1007 foreach my $decl (@declarations) {
241 19         40 _apply_handler_AH_($decl, 'CHECK');
242             }
243             }
244              
245             INIT {
246 4     4   15 $global_phase++;
247 4         17 foreach my $decl (@declarations) {
248 19         49 _apply_handler_AH_($decl, 'INIT');
249             }
250             }
251             }
252              
253             END {
254 4     4   2750 $global_phase++;
255 4         24 foreach my $decl (@declarations) {
256 19         48 _apply_handler_AH_($decl, 'END');
257             }
258             }
259              
260             1;
261             __END__