File Coverage

blib/lib/Attribute/Contract.pm
Criterion Covered Total %
statement 103 107 96.2
branch 24 32 75.0
condition 3 3 100.0
subroutine 22 23 95.6
pod 0 4 0.0
total 152 169 89.9


line stmt bran cond sub pod time code
1             package Attribute::Contract;
2              
3 4     4   127556 use strict;
  4         10  
  4         136  
4 4     4   19 use warnings;
  4         8  
  4         111  
5              
6 4     4   63 use 5.012;
  4         26  
  4         118  
7 4     4   4170 use attributes;
  4         7207  
  4         24  
8              
9             our $VERSION = '0.05';
10              
11 4     4   328 use constant NO_ATTRIBUTE_CONTRACT => $ENV{NO_ATTRIBUTE_CONTRACT};
  4         8  
  4         284  
12              
13 4     4   24 use Scalar::Util qw(refaddr);
  4         8  
  4         497  
14              
15 4     4   2264 use Attribute::Contract::Modifier::Requires;
  4         11  
  4         104  
16 4     4   2407 use Attribute::Contract::Modifier::Ensures;
  4         9  
  4         117  
17              
18             BEGIN {
19 4     4   21 use Exporter ();
  4         4  
  4         190  
20 4     4   8 our (@ISA, @EXPORT);
21              
22 4         58 @ISA = qw(Exporter);
23 4         2369 @EXPORT = qw(&MODIFY_CODE_ATTRIBUTES &FETCH_CODE_ATTRIBUTES);
24             }
25              
26             our $CONTRACT_REQUIRES_ATTR_ALIAS = 'ContractRequires';
27             our $CONTRACT_ENSURES_ATTR_ALIAS = 'ContractEnsures';
28              
29             my %attrs;
30             my %modifiers;
31             my %symcache;
32             my %todo;
33             my %import;
34              
35             sub contract_requires_name {
36 24 100   24 0 243 $import{-names}->{requires} || $CONTRACT_REQUIRES_ATTR_ALIAS;
37             }
38              
39             sub contract_ensures_name {
40 16 100   16 0 74 $import{-names}->{ensures} || $CONTRACT_ENSURES_ATTR_ALIAS;
41             }
42              
43             sub contract_attr_re {
44 16     16 0 43 my $requires_name = contract_requires_name();
45 16         34 my $ensures_name = contract_ensures_name();
46              
47 16         453 return qr/
48             ^
49             ($requires_name|$ensures_name)
50             (?:\((.*?)\))?
51             $
52             /x;
53             }
54              
55             sub import {
56 6     6   1381 return if NO_ATTRIBUTE_CONTRACT;
57              
58 6         21 my ($package) = caller;
59 6         17 $todo{$package}++;
60              
61 6         9 shift;
62 6         23 %import = @_;
63              
64 6         2391 __PACKAGE__->export_to_level(1);
65             }
66              
67             sub CHECK {
68 4     4   4033 return if NO_ATTRIBUTE_CONTRACT;
69              
70 4         30 foreach my $package (keys %todo) {
71 6         110 foreach my $key (keys %modifiers) {
72 9         238 my ($class, $method) = split /::/, $key;
73 9 100       86 next unless $package->isa($class);
74              
75 7 50       57 next unless my $code_ref = $package->can($method);
76              
77 7         19 my $attrs = $modifiers{$key};
78              
79 7         15 foreach my $attr (@$attrs) {
80 8 50       116 next unless $attr =~ contract_attr_re();
81              
82 8         42 attributes::->import($package, $code_ref, $attr);
83             }
84             }
85             }
86             }
87              
88             sub FETCH_CODE_ATTRIBUTES {
89 0     0   0 my ($package, $subref) = @_;
90              
91 0         0 my $attrs = $attrs{refaddr $subref };
92              
93 0         0 return @$attrs;
94             }
95              
96             sub MODIFY_CODE_ATTRIBUTES {
97 15     15   4489 my ($package, $code_ref, @attr) = @_;
98              
99 15         36 my $sym = findsym($package, $code_ref);
100 15         44 my $name = *{$sym}{NAME};
  15         35  
101              
102 15 50       83 return if exists $attrs{refaddr $code_ref };
103 15 100       74 return if exists $modifiers{"$package\::$name"};
104              
105 8         32 $attrs{refaddr $code_ref } = \@attr;
106 8         24 $modifiers{"$package\::$name"} = \@attr;
107              
108 8 50       21 if (@attr) {
109 4     4   82 no strict;
  4         8  
  4         286  
110 8         15 my @isa = @{"$package\::ISA"};
  8         34  
111 4     4   20 use strict;
  4         7  
  4         595  
112 8         20 foreach my $isa (@isa) {
113 2         5 my $key = "$isa\::$name";
114 2 50       7 if (exists $modifiers{$key}) {
115              
116 2         4 my $base_contract = $modifiers{$key};
117 2         5 my $contract = $modifiers{"$package\::$name"};
118              
119 2 50       6 if (@$base_contract == @$contract) {
120             next
121 2 100       11 if join(',', sort @$base_contract) eq
122             join(',', sort @$contract);
123             }
124              
125 1         264 Carp::croak(qq{Changing contract of method '$name'}
126             . qq{ in $package is not allowed});
127             }
128             }
129             }
130              
131 4     4   35 no warnings 'redefine';
  4         26  
  4         987  
132 7         12 foreach my $attr (@attr) {
133 8 50       24 next unless $attr =~ contract_attr_re();
134              
135 8         31 my $type = $1;
136 8         20 my $arguments = $2;
137              
138 8 100       18 my $modifier = $type eq contract_requires_name() ? 'Requires' : 'Ensures';
139              
140 8         18 my $class = __PACKAGE__ . '::Modifier::' . $modifier;
141              
142 8         62 *{$sym} = $class->modify($package, $name, $code_ref, \%import, $arguments);
  8         59  
143             }
144              
145 7         37 return ();
146             }
147              
148             # From Attribute::Handlers
149             sub findsym {
150 15     15 0 96 my ($package, $ref) = @_;
151              
152 15 100       83 return $symcache{$package, $ref} if $symcache{$package, $ref};
153              
154 14         25 my $type = ref($ref);
155              
156 4     4   20 no strict 'refs';
  4         11  
  4         167  
157 14         20 foreach my $sym (values %{$package . "::"}) {
  14         50  
158 4     4   18 use strict;
  4         7  
  4         513  
159 61 50       138 next unless ref(\$sym) eq 'GLOB';
160              
161 61         258 return $symcache{$package, $ref} = \$sym
162 61 100 100     62 if *{$sym}{$type} && *{$sym}{$type} == $ref;
  32         239  
163             }
164              
165 0           return;
166             }
167              
168             1;
169             __END__