File Coverage

lib/Variable/Watcher.pm
Criterion Covered Total %
statement 104 104 100.0
branch 21 24 87.5
condition 1 3 33.3
subroutine 23 23 100.0
pod 4 5 80.0
total 153 159 96.2


line stmt bran cond sub pod time code
1             package Variable::Watcher;
2              
3             require v5.6.0;
4              
5 1     1   59725 use strict;
  1         3  
  1         54  
6 1     1   6 use vars qw[$VERSION $AUTOLOAD $REPORT_FH $TRACE $VERBOSE];
  1         2  
  1         96  
7              
8 1     1   2711 use Attribute::Handlers;
  1         13146  
  1         7  
9 1     1   40 use Carp;
  1         1  
  1         72  
10 1     1   5 use Data::Dumper;
  1         2  
  1         44  
11 1     1   946 use Log::Message private => 1;
  1         24253  
  1         9  
12 1     1   259 use Params::Check qw[check allow];
  1         1  
  1         55  
13              
14 1     1   1056 use Tie::Scalar;
  1         661  
  1         25  
15 1     1   781 use Tie::Array;
  1         1288  
  1         36  
16 1     1   1104 use Tie::Hash;
  1         1096  
  1         144  
17              
18             $VERSION = '0.01';
19             $VERBOSE = 1;
20             $TRACE = 1;
21              
22             ### file handles to print to
23             local $| = 1;
24             $REPORT_FH = \*STDERR;
25              
26             ### list of names to use for the variables we're watching
27             my %Names = ();
28              
29             ### log::message object to store actions in
30             my $Log = new Log::Message;
31              
32             ### list of mappings of bless classes to tie classes
33             my %Map = (
34             SCALAR => 'Tie::StdScalar',
35             ARRAY => 'Tie::StdArray',
36             HASH => 'Tie::StdHash',
37             );
38              
39              
40             ### add ourselves to the callers @INC, so we can use attributes that
41             ### that are inherited.
42             sub import {
43 1     1   8 my $self = shift;
44 1         5 my $class = [caller]->[0];
45              
46 1     1   8 { no strict 'refs';
  1         3  
  1         362  
  1         2  
47 1         2 push @{"${class}::ISA"}, __PACKAGE__;
  1         2544  
48             }
49             }
50              
51             =head1 NAME
52              
53             Variable::Watcher -- Keep track of changes on C variables
54              
55             =head1 SYNOPSIS
56              
57             ### keep track of scalar changes
58             my $scalar : Watch(s) = 1;
59              
60             ### keep track of array changes
61             my @list : Watch(l) = (1);
62              
63             ### keep track of hash changes
64             my %hash : Watch(h) = (1 => 2);
65              
66              
67             ### retrieve individual mutations:
68             my @stack = Variable::Watcher->stack;
69            
70             ### retrieve the mutation as a printable string
71             my $string = Variable::Watcher->stack_as_string;
72              
73             ### flush the logs of all the mutations so far
74             Variable::Watcher->flush;
75            
76             ### Set the default reporting filehandle (defaults to STDERR
77             ### -- see the C section
78             $Variable::Watcher::REPORT_FH = \*MY_FH;
79            
80             ### Make Variable::Watcher not print to REPORT_FH when running
81             ### You will have to use the stack/stack_as_string method to
82             ### retrieve the logs. See the C section
83             $Variable::Watcher::VERBOSE = 0;
84              
85              
86             =head1 DESCRIPTION
87              
88             C allows you to keep track of mutations on C
89             variables. It will record every mutation you do to a variable that
90             is being Ced. You can retrieve these mutations as a list or
91             as a big printable string, filtered by a regex if you like.
92              
93             This is a useful debugging tool when you find your C
94             variables in a state you did not expect.
95              
96             See the C section for the limitations of this approach.
97              
98             =head1 Attributes
99              
100             =head2 my $var : Watch([NAME])
101              
102             In order to start Cing a variable, you must tag it as being
103             Ced at declaration time. You can optionally give it a name
104             to be used in the logs, rather than it's memory address (this is much
105             recommended).
106              
107             You can do this for perls three basic variable types;
108              
109             =over 4
110              
111             =item SCALAR
112              
113             To keep track of a scalar, and it's mutations, you could for example,
114             do somethign like this:
115              
116             my $scalar : Watch(s) = 1;
117             $scalar++;
118            
119              
120             The resulting output would be much like this:
121              
122             [Variable::Watcher s -> STORE] Performing 'STORE' on s passing
123             '1' at z.pl line 6
124             [Variable::Watcher s -> FETCH] Performing 'FETCH' on s at z.pl
125             line 7
126             [Variable::Watcher s -> STORE] Performing 'STORE' on s passing
127             '2' at z.pl line 7
128              
129             Showing you when you did the first C, when you retrieved the
130             value (C) and when you stored the increment (C).
131              
132             =item ARRAY
133              
134             To keep track of an array, and it's mutation, you could for example,
135             do something like this:
136              
137             my @list : Watch(l) = (1);
138             push @list, 2;
139             pop @list;
140              
141             The resulting output would be much like this:
142              
143             [Variable::Watcher l -> CLEAR] Performing 'CLEAR' on l at z2.pl
144             line 6
145             [Variable::Watcher l -> EXTEND] Performing 'EXTEND' on l
146             passing '1' at z2.pl line 6
147             [Variable::Watcher l -> STORE] Performing 'STORE' on l passing
148             '0 1' at z2.pl line 6
149             [Variable::Watcher l -> PUSH] Performing 'PUSH' on l passing
150             '2' at z2.pl line 7
151             [Variable::Watcher l -> FETCHSIZE] Performing 'FETCHSIZE' on l
152             at z2.pl line 7
153             [Variable::Watcher l -> POP] Performing 'POP' on l at z2.pl
154             line 8
155              
156             Showing you that you initialized an empty array (C), and
157             extended it's size (C) to fit your first assignment (C),
158             followed by the C which adds another value to your list.
159             Then we attempt to remove the last value, showing us how perl fetches
160             its size (C) and Cs the last value off.
161              
162             =item HASH
163              
164             To keep track of a hash, and it's mutation, you could for example,
165             do something like this:
166              
167             my %hash : Watch(h) = (1 => 2);
168             $hash{3} = 4;
169             delete $hash{3};
170              
171             The resulting output would be much like this:
172            
173             [Variable::Watcher h -> CLEAR] Performing 'CLEAR' on h at z3.pl
174             line 6
175             [Variable::Watcher h -> STORE] Performing 'STORE' on h passing
176             '1 2' at z3.pl line 6
177             [Variable::Watcher h -> STORE] Performing 'STORE' on h passing
178             '3 4' at z3.pl line 7
179             [Variable::Watcher h -> DELETE] Performing 'DELETE' on h
180             passing '3' at z3.pl line 8
181              
182             Showing you that you initialized an empty hash (C), and
183             Cd it's first key/value pair. Then we C the second
184             key/value pair, followed by a C of the key C<3>.
185              
186             =cut
187              
188             sub Watch : ATTR {
189 7     7 1 19182 my ($package, $symbol, $ref, $attr, $data, $phase) = @_;
190 7         21 my $reftype = ref $ref;
191              
192 7         16 my $obj;
193             ### do we support this type of ref?
194 7 100       57 unless( $Map{ $reftype } ) {
    100          
    100          
    50          
195              
196             ### report from the callers perspective, not from attribute.pm
197             ### or attribute::handlers perspective
198 1         4 local $Carp::CarpLevel += 2;
199              
200 1         278 carp("Cannot watch variable of type: '$reftype'" );
201 1         55 return;
202              
203             ### if so, tie it to the appropriate class
204             ### note that '$ref' is not the same as '$obj'!
205             } elsif ( $reftype eq 'SCALAR' ) {
206 4         49 tie $$ref, __PACKAGE__ .'::'. $reftype;
207 4         39 $obj = tied $$ref;
208              
209             } elsif ( $reftype eq 'ARRAY' ) {
210 1         20 tie @$ref, __PACKAGE__ .'::'. $reftype;
211 1         10 $obj = tied @$ref;
212              
213             } elsif ( $reftype eq 'HASH' ) {
214 1         18 tie %$ref, __PACKAGE__ .'::'. $reftype;
215 1         7 $obj = tied %$ref;
216             }
217              
218             ### store the name which we will call this variable in the
219             ### pretty print output
220 6   33     50 $Names{ $obj } = ($data || "$obj");
221              
222 6         19 return 1;
223 1     1   7 }
  1         3  
  1         12  
224              
225             sub AUTOLOAD {
226 455     455   21498 my $self = shift;
227 455         633 my $ref = tied $self;
228              
229             ### figure out the method called, and the class we're
230             ### blessed into
231 455         4343 my ($class,$method) = $AUTOLOAD =~ /::([^:]+)::([^:]+)$/;
232              
233             ### XXX we won't have a name yet at TIEFOO stage, but don't
234             ### bother reporting that either
235 455 100       2149 if( my $name = $Names{ $self } ) {
236 449         1624 my $msg = "Performing '$method' on $name";
237 449 100       3559 $msg .= " passing '@_'" if @_;
238              
239             ### skip the call frames that are private to this module
240 449         609 local $Carp::CarpLevel += 1;
241              
242 449         154597 $Log->store(
243             message => Carp::shortmess($msg),
244             tag => __PACKAGE__ . " $name -> $method",
245             level => 'report',
246             extra => [@_]
247             );
248             }
249              
250             ### get the coderef to the correpsonding function in
251             ### the tie class
252 455         7646 my $func = $Map{$class}->can( $method );
253              
254             ### called the tie function, with ourselves as primary
255             ### argument, and the rest of the args after that
256 455         1791 $func->($self, @_);
257             }
258              
259              
260             ### tie packages, which inherit straight from base
261             { package Variable::Watcher::SCALAR;
262 1     1   681 use base 'Variable::Watcher';
  1         10  
  1         176  
263              
264             package Variable::Watcher::ARRAY;
265 1     1   27 use base 'Variable::Watcher';
  1         6  
  1         79  
266              
267             package Variable::Watcher::HASH;
268 1     1   5 use base 'Variable::Watcher';
  1         1  
  1         442  
269             }
270              
271             =pod
272              
273             =head1 CLASS METHODS
274              
275             =head2 @stack = Variable::Watcher->stack( [name => $name, action => $action] );
276              
277             Retrieves a list of C objects describing the
278             mutations of the Ced variables.
279              
280             The optional C argument lets you filter based on the name you
281             have given the variables to be Ced.
282              
283             The optional C argument lets you filter on the type of action
284             you want to retrieve (C or C, etc).
285              
286             Refer to the C manpage for details on how to work with
287             C objects.
288              
289             =cut
290              
291             ### report stack retrieval and manipulation
292             sub stack {
293 8     8 1 16 my $self = shift;
294 8         23 my %hash = @_;
295              
296 8         15 my($name,$action);
297 8         69 my $tmpl = {
298             name => { default => '', store => \$name },
299             action => { default => '', store => \$action },
300             };
301              
302 8 50       51 check( $tmpl, \%hash ) or return;
303              
304 8         648 my @rv;
305 8         19 my $re = __PACKAGE__ . '\s(.+?)\s->\s(.+?)$';
306              
307 8         58 for my $item ( $Log->retrieve( chrono => 1 ) ) {
308 459         27146 my ($tagname,$tagaction) = $item->tag =~ /$re/;
309              
310             ### you want to do name based retrieving?
311 459 100       6075 if( $name ) {
312 8 50       23 next unless allow( $tagname, $name );
313             }
314              
315             ### you want to do action based retrieving?
316 451 100       864 if( $action ) {
317 4 100       13 next unless allow( $tagaction, $action);
318             }
319              
320 449         755 push @rv, $item;
321             }
322              
323 8         446 return @rv;
324             }
325              
326             =head2 $string = Variable::Watcher->stack_as_string( [name => $name, action => $action] );
327              
328             Returns the mutation log as a printable string, optionally filterd on
329             the criteria as described in the C method.
330              
331             =cut
332              
333             sub stack_as_string {
334 8     8 1 36669 my $class = shift;
335 8         43 my @stack = $class->stack( @_ );
336              
337 449         8385 return join '', map {
338 8         46 '[' . $_->tag . '] ' . $_->message;
339             } @stack
340             }
341              
342             =head2 @stack = Variable::Watcher->flush;
343              
344             Flushes the logs of all mutations that have occurred so far. Returns
345             the stack, like the C method would, without filtering.
346              
347             =cut
348              
349              
350             sub flush {
351 5     5 1 33702 return reverse $Log->flush;
352             }
353              
354             ### the function that pretty prints the actions performed on variables
355             { package Log::Message::Handlers;
356 1     1   6 use Carp ();
  1         1  
  1         111  
357              
358             sub report {
359 449     449 0 788410 my $self = shift;
360              
361             ### so you don't want us to print the msg? ###
362 449 100       2713 return unless $Variable::Watcher::VERBOSE;
363              
364             ### store the old filehandle, select the one the user wants us
365             ### to print to
366 2         9 my $old_fh = select $Variable::Watcher::REPORT_FH;
367 2         16 print '['. $self->tag (). '] ' . $self->message;
368              
369             ### restore the old filehandle
370 2         53 select $old_fh;
371              
372 2         10 return;
373             }
374             }
375              
376             1;
377              
378             __END__