File Coverage

blib/lib/Devel/Carnivore.pm
Criterion Covered Total %
statement 96 107 89.7
branch 20 26 76.9
condition n/a
subroutine 26 30 86.6
pod 0 5 0.0
total 142 168 84.5


line stmt bran cond sub pod time code
1             package Devel::Carnivore;
2 1     1   21948 use strict;
  1         3  
  1         41  
3 1     1   14 use 5.6.0;
  1         12  
  1         39  
4 1     1   4 use warnings;
  1         7  
  1         41  
5 1     1   4 use Carp;
  1         2  
  1         80  
6 1     1   930 use Attribute::Handlers;
  1         6846  
  1         7  
7 1     1   44 no warnings "redefine";
  1         3  
  1         35  
8              
9 1     1   5 use vars qw/$OUT @EXPORT @ISA $NAME $VERSION/;
  1         1  
  1         90  
10              
11 1     1   6 use base "Exporter";
  1         2  
  1         380  
12             @EXPORT = qw(watch unwatch);
13              
14             $VERSION = 0.09;
15              
16             # By default print to STDERR
17             $OUT = \*STDERR;
18              
19             # test whether the first para is NOT a hashref
20             sub test_no_hashref($) {
21 20     20 0 39 my($hashref) = @_;
22            
23 20 100       84 return if ref $hashref eq "HASH"; # normal hash ref: good
24            
25 11 100       37 if(ref $hashref) {
26 9         15 local $@;
27 9         795 eval '%{ $hashref }';
28 9 100       73 return unless $@; # blessed hash: good
29             }
30            
31 6         707 return 1;
32             }
33              
34             # tie $hashref to Devel::Carnivore::Tie::Hash
35             # optionally specify a name
36             # for internal use: a custom carp level may be specified. look at Carp.pm for documentation
37             sub watch($;$) {
38 9     9 0 16218 my($hashref,$name) = @_;
39            
40             # this module only works with hashrefs
41 9 100       24 croak "variable is not a hash reference" if test_no_hashref $hashref;
42            
43 7         30 my %copy = %$hashref; # make a copy of the actual hash in hashref
44            
45 7         32 my $calling_pkg = caller;
46            
47 7 100       159 croak "can't watch a variable which is already tied" if tied %$hashref;
48            
49             # print a comment that we start watching
50 6         63 print $Devel::Carnivore::OUT "# variable is now under observation\n";
51            
52 6         44 tie %$hashref, 'Devel::Carnivore::Tie::Hash', $name;
53             # %$hashref is now empty
54            
55 6         39 while(my($key,$value) = each %copy) { # but we restore the copy
56 0         0 $hashref->{$key} = $value
57             }
58             }
59              
60             # untie $hashref
61             sub unwatch($) {
62 4     4 0 2301 my($hashref) = @_;
63            
64 4 50       15 if(test_no_hashref $hashref) { # of course this only works if $hashref is actually a hash reference
    50          
65 0         0 carp "variable is not a hash reference"
66             }
67             elsif((tied %$hashref)->isa("Devel::Carnivore::Tie::Hash")) {
68 1     1   5 no warnings; # silence "untie attempted while 1 inner references still exist" warning
  1         2  
  1         145  
69             # is there a better way to do this.
70             # as far as I can see this call is perfectly safe.
71 4         31 untie %$hashref;
72 4         16 print $Devel::Carnivore::OUT "# mission completed\n";
73             } else {
74 0         0 carp "Apparently this variable is not currently under observation."
75             }
76             }
77              
78             # install Watch as a universal attribute for hashes
79             # a name may be given as the single parameter to the attribute
80             # we then call our watch with the hashref and the name
81             sub UNIVERSAL::Watch : ATTR(HASH) {
82 1     1 0 1023 my ($package, $symbol, $hashref, $attr, $name, $phase) = @_;
83            
84 1         3 watch $hashref, $name
85 1     1   7 }
  1         2  
  1         5  
86            
87             # install Watch as a universal attribute for scalars
88             # this scalar is then tied to the special class Devel::Carnivore::Tie::Scalar
89             # ....why the f*ck? ... oh, yeah, so we can automatically tie any hashref to
90             # Devel::Carnivore::Tie::Hash as soon as it is assigned to this scalar :)
91             sub UNIVERSAL::Watch : ATTR(SCALAR) {
92 5     5 0 43024 my ($package, $symbol, $scalar_ref, $attr, $name, $phase) = @_;
93            
94 5         38 tie $$scalar_ref, 'Devel::Carnivore::Tie::Scalar', $name
95 1     1   350 }
  1         3  
  1         11  
96              
97             # utility class used by the Watch scalar attribute
98             package Devel::Carnivore::Tie::Scalar;
99 1     1   230 use Carp;
  1         2  
  1         81  
100 1     1   11 use Devel::Carnivore;
  1         2  
  1         744  
101              
102             # save a scalar and a name with the tied scalar
103             sub TIESCALAR {
104 5     5   21 my($class,$name) = @_;
105            
106 5         7 my $scalar = undef;
107            
108 5         23 my $self = {
109             scalar => \$scalar,
110             name => $name,
111             };
112            
113 5         32 bless $self, $class;
114             }
115              
116             # only hashrefs or object based on hashrefs may be assigned to scalars based on this class
117             # these hashrefs are then immediately tied to Devel::Carnivore::Tie::Hash
118             sub STORE {
119 7     7   612 my($self,$value) = @_;
120            
121 7 100       22 croak "You may only store hashrefs within a scalar under observation by Devel::Carnivore."
122             if Devel::Carnivore::test_no_hashref($value);
123            
124 3         22 Devel::Carnivore::watch $value, $self->{name};
125            
126 3         4 ${$self->{scalar}} = $value
  3         14  
127             }
128              
129 2     2   7 sub FETCH { ${$_[0]->{scalar}} }
  2         12  
130 5     5   217 sub DESTROY { undef ${$_[0]->{scalar}} }
  5         35  
131              
132             package Devel::Carnivore::Tie::Hash;
133              
134             # this is where the actual output is generated
135             sub STORE {
136 32     32   7297 my($self,$key,$value) = @_;
137 32         81 my($package,$filename,$line) = caller;
138            
139 32         63 my $hashref = $self->{hash};
140 32         42 my $name = $self->{name};
141            
142 32         45 my $old_value = $hashref->{$key};
143              
144 32 50       63 $key = defined $key ? $key : ""; # some stuff to limit warnings
145 32 50       49 $value = defined $value ? $value : "";
146 32 100       57 $old_value = defined $old_value ? $old_value : "";
147            
148 32         40 my $message = "> "; # this is what will eventually get printed out
149            
150 32 50       132 $message .= qq{$name: } if defined $name; # we start out with a name if we have one
151            
152             # "myHashKey" changed from "someValue" to "someOtherValue"
153 32         73 $message .= qq{"$key" changed from "$old_value" to "$value" };
154            
155            
156             # $Carp::CarpLevel may be set to influence the output.
157             # This sucks bad!!!. First, CarpLevel is deprecated but setting CarpInternal to our
158             # caller does not seem to work, second, setting CarpLevel to 1 seems to be the right
159             # thing but Perl 5.6.1 (and below???) doesnt like it.
160            
161 32         45 local $Carp::CarpLevel;
162 32 50       79 if($] >= 5.008) {
163 32         110 $Carp::CarpLevel = 1;
164             }
165            
166             # we print this using a function available via the Carp module
167             # it automatically adds information about where this method was called
168            
169 32         5350 print $Devel::Carnivore::OUT Carp::shortmess($message);
170            
171             # ahh, and finally we behave like a normal hash.
172 32         1513 return $hashref->{$key} = $value;
173             }
174              
175             # make an object with a hash and a name
176             sub TIEHASH {
177 6     6   12 my($class,$name) = @_;
178            
179 6         42 bless {
180             hash => {},
181             name => $name,
182             }, $class
183             }
184              
185             # copied from Tie::Hash, adapted to my object scheme
186 48     48   625 sub FETCH { $_[0]->{hash}{$_[1]} }
187 0     0   0 sub FIRSTKEY { my $a = scalar keys %{$_[0]->{hash}}; each %{$_[0]->{hash}} }
  0         0  
  0         0  
  0         0  
188 0     0   0 sub NEXTKEY { each %{$_[0]->{hash}} }
  0         0  
189 0     0   0 sub EXISTS { exists $_[0]->{hash}{$_[1]} }
190 0     0   0 sub DELETE { delete $_[0]->{hash}{$_[1]} }
191 1     1   126 sub CLEAR { %{$_[0]->{hash}} = () }
  1         5  
192              
193             q
194              
195             __END__