File Coverage

blib/lib/Debug/Mixin.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1              
2             package Debug::Mixin ;
3 10     10   468226 use base Exporter ;
  10         26  
  10         826  
4              
5 10     10   57 use strict ;
  10         21  
  10         360  
6 10     10   48 use warnings ;
  10         28  
  10         366  
7              
8             BEGIN
9             {
10 10     10   53 use vars qw ($VERSION @EXPORT_OK %EXPORT_TAGS);
  10         16  
  10         856  
11              
12 10     10   23 $VERSION = '0.4' ;
13 10         28 @EXPORT_OK = qw (IsDebuggerEnabled CheckBreakpoint);
14 10         269 %EXPORT_TAGS = ();
15             }
16              
17             #-------------------------------------------------------------------------------
18              
19 10     10   72 use Carp qw(croak carp confess cluck);
  10         18  
  10         710  
20 10     10   15981 use Data::TreeDumper ;
  0            
  0            
21              
22             use Sub::Install;
23             use English qw( -no_match_vars ) ;
24              
25             use Readonly ;
26             Readonly my $EMPTY_STRING => q{} ;
27              
28             use Tie::Hash::Indexed ;
29             use List::MoreUtils qw(any) ;
30              
31             #-------------------------------------------------------------------------------
32              
33             =head1 NAME
34              
35             Debug::Mixin - Make your applications and modules easier to debug
36              
37             =head1 SYNOPSIS
38              
39             package my_module ;
40              
41             # load Debug::Mixin
42             use Debug::Mixin
43             {
44             BANNER => "my banner",
45            
46             # available at any point in the debugger
47             DEBUGGER_SUBS =>
48             [
49            
50             {
51             NAME => CheckDependencyMatrix
52             ALIASES => [qw(dm_cdm cdm)],
53             DESCRIPTION => "a short description of what this sub is for",
54             HELP => "a long, possibly multi line description displayed"
55             . "when the user needs it",
56             SUB => sub{},
57             }
58             ],
59             } ;
60            
61             # add breakpoints
62             AddBreakpoint
63             (
64             NAME => 'hi'
65            
66             FILTERS =>
67             [
68             sub
69             {
70             my (%filter) = @_ ;
71             $filter{ARGS}{TYPE} eq 'DEPEND' ; # true will enable the actions
72             }
73             ]
74            
75             ACTIONS =>
76             [
77             sub
78             {
79             my (%filter) = @_ ;
80            
81             print DumpTree $filter{ARGS}{COMPLEX_ELEMENT} ;
82            
83             return JUMP_TO_DEBUGGER # want to jump into debugger
84             }
85             ]
86            
87             DEBUGGER_SUBS=>
88             [
89             {
90             NAME => 'BreakpointDebuggerSub',
91             DESCRIPTION => "a short description of what this sub is for",
92             HELP => "a long, possibly multi line description displayed"
93             . "when the user needs it",
94             SUB => sub{},
95             }
96             ]
97            
98             LOCAL_STORAGE => {}
99            
100             ALWAYS_USE_DEBUGGER => 0 # let subs decide if we jump in the debugger
101             ACTIVE => 1
102             )
103            
104             # use the breakpoints
105             sub DoSomething
106             {
107             #DEBUG
108             my %debug_data =
109             (
110             TYPE => 'VARIABLE'
111             , VARIABLE_NAME => $key
112             , VARIABLE_VALUE => $value
113             , ...
114             ) ;
115            
116             #DEBUG
117             $DB::single = 1 if(Debug::Mixin::IsDebuggerEnabled() && Debug::Mixin::CheckBreakpoint(%debug_data)) ;
118            
119             # or
120            
121             if(Debug::Mixin::IsDebuggerEnabled())
122             {
123             %debug_data =
124             (
125             TYPE => 'VARIABLE'
126             , ...
127             ) ;
128            
129             $DB::single = 1 if(Debug::Mixin::CheckBreakpoint(%debug_data, MORE_DATA => 1)) ;
130             }
131            
132              
133             =head1 DESCRIPTION
134              
135             This module help you define breakpoints for your own module or applications making them easier to
136             debug.
137              
138             =head1 DOCUMENTATION
139              
140             Lately,I've been speculating about architectures that would allow us to debug them more easily. Logging,
141             aspect oriented, web interface to internals are some examples of techniques already in use.
142              
143             The perl debugger already allows us to do a lot of tricky testing before displaying a prompt or
144             stopping only when certain conditions are met. I believe in making debugging even more practical
145             and intelligent.
146              
147             My theory is simple, actively present data, from your code, and check if a breakpoint matches. This
148             is, in theory, not very different from smart breakpoints in the debugger except the breakpoints are
149             defined in files outside the debugger and are part of the module distribution. The place where this
150             breakpoints triggers are not defined by the breakpoints but by the code being debugged.
151              
152             Finding where the breakpoints should be checked is best determined while writing the code though
153             they can be added later making your module more . This, of course, doesn't stop you
154             from using the debugger in a normal fashion, with or without the help of these "code called" breakpoints
155              
156             In your module
157              
158             use Debug::Mixin ;
159             ...
160             $DB::single = 1 if(Debug::Mixin::IsDebuggerEnabled() && Debug::Mixin::CheckBreakpoint(%debug_data)) ;
161              
162             At the cost of a subroutine call, you get checking of breakpoints at a position you deem strategic and the possibility
163             to stop in the debugger if any of the breakpoints actions flag to stop.
164              
165             I'd check if the cost has a real impact before trying to reduce it. you could write:
166              
167             use Filter::Uncomment GROUPS => [ debug_mixin => ['DM'] ] ;
168             use Debug::Mixin ;
169             ...
170             ##DM $DB::single = 1 if(Debug::Mixin::IsDebuggerEnabled() && Debug::Mixin::CheckBreakpoint(%debug_data)) ;
171              
172             You'll now pay only if you are actively using B to debug your application/modules. The only
173             cost being the filtration of the code if, and only if, you decide to uncomment. if you don't, the cost is practically zero.
174              
175             Have I used this in any real project, PBS on CPAN, and it did really help a lot with very complex problems. Mainly
176             because it let me run debugging very fast but also because the check point were put in the code before I had
177             any problems saving me time to find out where I should place them.
178              
179             =head1 DEBUG SESSION
180              
181             script Debug::Mixin aware
182              
183             perl -d script.pl --argument_loading_plenty_breakpoints
184              
185             script doesn't have to be aware of modules debugging facilities, only modules using Debug::Mixin have to
186              
187             perl -d -MDebug::Mixin='LoadBreakpointsFiles=file' script.pl
188              
189             > Using Debug::Mixin banner, use 'dm_help' for Debug::Mixin help.
190            
191             > dm_help
192             dm_subs() list and run debugging subs
193             dm_load(@files) load breakpoints files
194            
195             # all breakpoints functions take a regex
196             dm_bp(qr//) list breakpoints
197             dm_activate(qr//) activate breakpoints
198             dm_deactivate(qr//) deactivate breakpoints
199             dm_use_debugger(qr//) jump in debugger
200             dm_dont_use_debugger(qr//) jump in debugger only if a breakpoint action says to
201            
202             > run part of the program ...
203            
204             > Breakpoints display information (eventually interacting with the user)
205            
206             > stop at a breakpoint, if local commands are available interact with the user, display their documentation
207              
208             =head1 SUBROUTINES/METHODS
209              
210             =cut
211              
212             if(*DB::DB{CODE})
213             {
214             Output("Debug::Mixin support available, type 'dm_help' for help, or man Debug::Mixin for more help.\n\n") ;
215             }
216             else
217             {
218             Output("Debug::Mixin banner when debugger is not loaded\n") ;
219             }
220            
221             #-------------------------------------------------------------------------------
222              
223             my $debug_enabled = 1 ;
224             my %debugger_subs;
225             tie my %breakpoints, 'Tie::Hash::Indexed' ; ## no critic
226              
227             #-------------------------------------------------------------------------------
228              
229             sub import
230             {
231              
232             =head2 import
233              
234             Called for you by Perl
235              
236             =cut
237              
238             my ($module_name, $data, @more_data) = @_ ;
239              
240             my ($package, $file_name, $line) = caller() ;
241             Output("Debug::Mixin used at '$package, $file_name, $line'\n") ;
242              
243             #~ use Data::TreeDumper ;
244             #~ print DumpTree \@_ ;
245              
246             if(defined $data)
247             {
248             if('HASH' eq ref $data)
249             {
250             while( my($key, $value) = each %{$data})
251             {
252             SetupElement($package, $file_name, $line, $key, $value) ;
253             }
254             }
255             else
256             {
257             unshift @more_data, $data ;
258             for(@more_data)
259             {
260             SetupElement($package, $file_name, $line, split(/=/sxm, $_)) ;
261             }
262             }
263             }
264            
265             # this module doesnt export any subroutine
266             #~ Debug::Mixin->export_to_level(1, @_);
267              
268             #except
269             if(*DB::DB{CODE})
270             {
271            
272            
273             for my $sub
274             (
275             [\&dm_help, 'dm_help'],
276            
277             [\&dm_subs , 'dm_subs'],
278            
279             [\&LoadBreakpointsFiles , 'dm_load'],
280             [\&ListBreakpoints , 'dm_bp'],
281            
282             [\&ActivateBreakpoints, 'dm_activate'],
283             [\&DeactivateBreakpoints, 'dm_deactivate'],
284             [\&ActivateAlwaysUseDebugger , 'dm_use_debugger'],
285             [\&DeactivateAlwaysUseDebugger , 'dm_dont_use_debugger'],
286             )
287             {
288             my ($code, $as) = @{$sub} ;
289            
290             Sub::Install::reinstall_sub
291             ({
292             code => $code,
293             into => 'main',
294             as => $as,
295             });
296             }
297             }
298            
299             return(1) ;
300             }
301              
302             #-------------------------------------------------------------------------------
303              
304             sub SetupElement
305             {
306              
307             =head2 SetupElement
308              
309             Private function
310              
311             =cut
312              
313             my ($package, $file_name, $line, $key, $value) = @_ ;
314              
315             for($key)
316             {
317             /BANNER/smix and do
318             {
319             if(*DB::DB{CODE})
320             {
321             Output("Debug::Mixin loaded for '$value'\n") ;
322             }
323             next ;
324             } ;
325            
326             /LoadBreakpointsFiles/smx and do
327             {
328             my $files = 'ARRAY' eq ref $value ? $value : [$value] ;
329            
330             for my $file (@{$files})
331             {
332             Output("Loading '$file'\n") ;
333             }
334             next ;
335             } ;
336            
337             /DEBUGGER_SUBS/smx and do
338             {
339             croak "Debug::Mixin: DEBUGGER_SUBS must be a list!\n" unless('ARRAY' eq ref $value) ;
340             croak "Debug::Mixin: no subroutine defined in DEBUGGER_SUBS!\n" if( @{$value} <= 0) ;
341            
342             Readonly my $EXPECTED_NUMBER_OF_DEBUGGER_SUB_FIELDS => 5 ;
343            
344             for my $debugger_sub (@{$value})
345             {
346             croak "Debug::Mixin: local subroutine must be a hash!\n" unless 'HASH' eq ref $debugger_sub ;
347             croak "Debug::Mixin: invalid local subroutine definition!\n"
348             unless $EXPECTED_NUMBER_OF_DEBUGGER_SUB_FIELDS == keys %{$debugger_sub} ;
349            
350             my $valid_keys = join('$|^', qw(NAME ALIASES DESCRIPTION HELP SUB)) ; ## no critic
351            
352             for my $key (keys %{$debugger_sub})
353             {
354             croak "Debug::Mixin: Unrecognized local subroutine argument '$key'!\n" unless $key =~ /^$valid_keys$/smxo ;
355             }
356            
357             if(*DB::DB{CODE})
358             {
359             $debugger_subs{$package}{$debugger_sub->{NAME}} = $debugger_sub ;
360            
361             Sub::Install::reinstall_sub
362             ({
363             code => $debugger_sub->{SUB},
364             into => $package,
365             as => $debugger_sub->{NAME},
366             });
367            
368             for my $alias ($debugger_sub->{ALIASES})
369             {
370             Sub::Install::reinstall_sub
371             ({
372             code => $debugger_sub->{SUB},
373             into => $package,
374             as => $alias,
375             });
376             }
377            
378             Output("Debug::Mixin registrating debugger sub '${package}::$debugger_sub->{NAME}'\n") ;
379             }
380             }
381            
382             next ;
383             } ;
384            
385             croak "Unknown setup element '$key'!\n" ;
386             }
387            
388             return(1) ;
389             }
390              
391             #-------------------------------------------------------------------------------
392              
393             sub EnableDebugger
394             {
395              
396             =head2 EnableDebugger
397              
398             Globally Enables or disables this module.
399              
400             Debug::Mixin::EnableDebugger(0) ;
401             Debug::Mixin::EnableDebugger(1) ;
402              
403             =cut
404              
405             $debug_enabled = shift ;
406              
407             return($debug_enabled) ;
408             }
409              
410             #-------------------------------------------------------------------------------
411              
412             sub IsDebuggerEnabled
413             {
414              
415             =head2 IsDebuggerEnabled
416              
417             Returns the state of this module.
418              
419             my $status = Debug::Mixin::IsDebuggerEnabled() ;
420              
421             =cut
422              
423             return($debug_enabled) ;
424             }
425              
426             #-------------------------------------------------------------------------------
427              
428             sub AddBreakpoint ## no critic (Subroutines::RequireArgUnpacking)
429             {
430              
431             =head2 AddBreakpoint
432              
433             use Debug::Mixin ;
434            
435             AddBreakpoint
436             (
437             NAME => 'add dependencies'
438            
439             FILTERS =>
440             [
441             sub
442             {
443             my (%filter) = @_ ;
444             $filter{ARGS}{TYPE} eq 'DEPEND' ; # true will enable the actions
445             }
446             ]
447            
448             ACTIONS =>
449             [
450             sub
451             {
452             my (%filter) = @_ ;
453            
454             print DumpTree $filter{ARGS}{COMPLEX_ELEMENT} ;
455            
456             return JUMP_TO_DEBUGGER # want to jump into debugger
457             }
458             ]
459            
460             DEBUGGER_SUBS =>
461             [
462             {
463             NAME => 'CheckDependencyMatrix',
464             ALIASES => [qw(dm_cdm cdm)],
465             DESCRIPTION => "a short description of what this sub is for",
466             HELP => "a long, possibly multi line description displayed"
467             . "when the user needs it",
468             SUB => sub{},
469             }
470             ]
471            
472             LOCAL_STORAGE => {}
473            
474             ALWAYS_USE_DEBUGGER => 0 # let subs decide if we jump in the debugger
475             ACTIVE => 1
476             )
477              
478              
479             =head2 Breakpoint elements
480              
481             =over 2
482              
483             =item * NAME
484              
485             The name of the breakpoint, you can remove and otherwise manipulate breakpoints by name.
486              
487             =item * FILTERS
488              
489             Used to enable or disable all the actions with a single check. FILTERS is a list of sub references. The
490             references are passed the argument you pass to L and :
491              
492             =over 2
493              
494             =item * DEBUG_MIXIN_BREAKPOINT
495              
496             A reference to the breakpoint.
497              
498             =item * DEBUG_MIXIN_CALLED_AT
499              
500             a hash containing the file and line where L was called.
501              
502             =back
503              
504             =item * ACTIONS
505              
506             B is a list of sub references. All the subs are run. All debugging functionality
507             (ex: activating or adding breakpoints) are available within the subs.
508              
509             =item * DEBUGGER_SUBS
510              
511             List of functions available, at the time the breakpoint matches, when running under the debugger.
512             Debug::Mixin will present you with the list of local functions and allow you to run any of the functions.
513              
514             each entry must have follow the following format
515              
516             {
517             NAME => 'CheckDependencyMatrix',
518             ALIASES => [qw(dm_cdm cdm)],
519             DESCRIPTION => "a short description of what this sub is for",
520             HELP => "a long, possibly multi line description displayed"
521             . "when the user needs it",
522             SUB => sub{},
523             }
524              
525             =item * ALWAYS_USE_DEBUGGER
526              
527             If the breakpoint is active, L will always return true.
528              
529             =item * ACTIVE
530              
531             The breakpoint actions will only be called if B is set.
532              
533             =item * LOCAL_STORAGE
534              
535             A user storage area within the breakpoint. You can store and manipulate it as you wish. You must use
536             this area as Debug::Mixin only allows certain fields in a breakpoint.
537              
538             This item can be manipulated through the breakpoint reference passed to filters and actions.
539              
540             =back
541              
542             A warning is displayed if you override an existing breakpoint. A breakpoint creation history is kept.
543              
544             =cut
545              
546             croak 'AddBreakpoint: odd number of arguments!' if @_ % 2 ;
547              
548             my (%breakpoint) = @_ ;
549              
550             CheckBreakPointDefinitions(\%breakpoint) ;
551              
552             my ($package, $file_name, $line) = caller() ;
553              
554             unless (exists $breakpoints{$breakpoint{NAME}})
555             {
556             $breakpoints{$breakpoint{NAME}} = \%breakpoint ;
557             }
558             else
559             {
560             carp ("Redefining breakpoint '$breakpoint{NAME}' at '$file_name:$line'.\n") ;
561            
562             #keep history
563             my $at = $breakpoints{$breakpoint{NAME}}{AT} ;
564             $breakpoints{$breakpoint{NAME}} = \%breakpoint ;
565            
566             $breakpoints{$breakpoint{NAME}}{AT} = $at ;
567             }
568              
569             push @{$breakpoints{$breakpoint{NAME}}{AT}}, {FILE => $file_name, LINE => $line, PACKAGE => $package} ;
570              
571             return(1) ;
572             }
573              
574             #----------------------------------------------------------------------
575              
576             sub CheckBreakPointDefinitions
577             {## no critic (ProhibitExcessComplexity)
578              
579             =head2 CheckBreakPointDefinitions
580              
581             Checks the validity of the user supplied breakpoint definitions. Croaks on error.
582              
583             =cut
584              
585             my ($breakpoint) = @_ ;
586              
587             my $valid_keys = join('$|^', qw(NAME FILTERS ACTIONS DEBUGGER_SUBS LOCAL_STORAGE ALWAYS_USE_DEBUGGER ACTIVE)) ; ## no critic
588              
589             for my $key (keys %{$breakpoint})
590             {
591             croak "AddBreakpoint: Unrecognized argument '$key'!\n" unless $key =~ /^$valid_keys$/smox ;
592             }
593              
594             croak "AddBreakpoint: Missing NAME!\n" unless exists $breakpoint->{NAME} && defined $breakpoint->{NAME} ;
595             croak "AddBreakpoint: NAME must be a scalar!\n" unless $EMPTY_STRING eq ref $breakpoint->{NAME} ;
596              
597             if(exists $breakpoint->{ACTIONS})
598             {
599             croak "AddBreakpoint: ACTIONS must be a list of subs!\n" unless 'ARRAY' eq ref $breakpoint->{ACTIONS} ;
600             croak "AddBreakpoint: no actions defined in ACTIONS!\n" if @{$breakpoint->{ACTIONS}} <= 0 ;
601             croak "AddBreakpoint: actions is not a sub reference!\n" if any {'CODE' ne ref $_} @{$breakpoint->{ACTIONS}} ;
602             }
603              
604             if(exists $breakpoint->{FILTERS})
605             {
606             croak "AddBreakpoint: FILTERS must be an array!\n" unless 'ARRAY' eq ref $breakpoint->{FILTERS} ;
607             croak "AddBreakpoint: no filters defined in FILTERS!\n" if @{$breakpoint->{FILTERS}} <= 0 ;
608             croak "AddBreakpoint: filter is not a code ref!\n" if any {'CODE' ne ref $_} @{$breakpoint->{FILTERS}} ;
609             }
610            
611             unless
612             (
613             exists $breakpoint->{ACTIONS}
614             ||
615             (exists $breakpoint->{FILTERS} && exists $breakpoint->{ALWAYS_USE_DEBUGGER} && $breakpoint->{ALWAYS_USE_DEBUGGER} == 1)
616             )
617             {
618             croak "AddBreakpoint: Missing ACTIONS or (FILTERS + ALWAYS_USE_DEBUGGER)!\n"
619             }
620              
621             if(exists $breakpoint->{DEBUGGER_SUBS})
622             {
623             croak "AddBreakpoint: DEBUGGER_SUBS must be a list!\n" unless 'ARRAY' eq ref $breakpoint->{DEBUGGER_SUBS} ;
624             croak "AddBreakpoint: no subroutine defined in DEBUGGER_SUBS!\n" if @{$breakpoint->{DEBUGGER_SUBS}} <= 0 ;
625            
626             Readonly my $EXPECTED_NUMBER_OF_DEBUGGER_SUB_FIELDS => 4 ;
627            
628             for my $debugger_sub (@{$breakpoint->{DEBUGGER_SUBS}})
629             {
630             croak "AddBreakpoint: local subroutine must be a hash!\n" unless 'HASH' eq ref $debugger_sub ;
631             croak "AddBreakpoint: invalid local subroutine definition!\n"
632             unless $EXPECTED_NUMBER_OF_DEBUGGER_SUB_FIELDS == keys %{$debugger_sub} ;
633            
634             my $valid_function_keys = join('$|^', qw(NAME DESCRIPTION HELP SUB)) ; ## no critic
635            
636             for my $key (keys %{$debugger_sub})
637             {
638             croak "AddBreakpoint: Unrecognized local subroutine argument '$key'!\n" unless $key =~ /^$valid_function_keys$/smox ;
639             }
640             }
641             }
642            
643             croak "AddBreakpoint: ALWAYS_USE_DEBUGGER must be a scalar!\n" if exists $breakpoint->{ALWAYS_USE_DEBUGGER}&& $EMPTY_STRING ne ref $breakpoint->{ALWAYS_USE_DEBUGGER} ;
644             croak "AddBreakpoint: ACTIVE must be a scalar!\n" if exists $breakpoint->{ACTIVE}&& $EMPTY_STRING ne ref $breakpoint->{ACTIVE} ;
645              
646             return ;
647             }
648              
649             #----------------------------------------------------------------------
650              
651             sub LoadBreakpointsFiles
652             {
653              
654             =head2 LoadBreakpointsFiles
655              
656             Evaluates a perl script. The main purpose of the script is to define breakpoints but the script
657             can also query Debug::Mixin and change existing breakpoints or run any perl code deemed fit.
658              
659             Croaks on error, return(1) on success.
660              
661             =cut
662              
663             my (@files) = @_ ; # can contains breakpoint definitions
664              
665             for my $file (@files)
666             {
667             if($file ne $EMPTY_STRING)
668             {
669             unless (my $return = do $file )
670             {
671             croak "couldn't parse '$file': $EVAL_ERROR" if $EVAL_ERROR;
672             croak "couldn't do '$file': $OS_ERROR" unless defined $return;
673             #~ croak "couldn't run '$file'" unless $return;
674             }
675             }
676             }
677            
678             return(1) ;
679             }
680              
681             #----------------------------------------------------------------------
682              
683             sub RemoveBreakpoints
684             {
685              
686             =head2 RemoveBreakpoints
687              
688             Removes one or more breakpoint matching the name regex passed as argument. A warning is displayed
689             for each removed breakpoint.
690              
691             Debug::Mixin::RemoveBreakpoints(qr/dependencies/) ;
692              
693             Returns the number of removed breakpoints.
694              
695             =cut
696              
697             my ($breakpoint_regex) = @_ ;
698             $breakpoint_regex ||= q{.} ;
699              
700             my $removed_breakpoints = 0 ;#bp local subs
701              
702             for my $breakpoint_name (sort keys %breakpoints)
703             {
704             if($breakpoint_name =~ $breakpoint_regex)
705             {
706             carp("Debug::Mixin: Breakpoint '$breakpoint_name' removed.\n") ;
707             delete $breakpoints{$breakpoint_name} ;
708             $removed_breakpoints++ ;
709             }
710             }
711              
712             return($removed_breakpoints) ;
713             }
714              
715             #----------------------------------------------------------------------
716              
717             sub RemoveAllBreakpoints
718             {
719              
720             =head2 RemoveAllBreakpoints
721              
722             Removes all breakpoints. No message is displayed.
723              
724             Debug::Mixin::RemoveAllBreakpoints();
725              
726             =cut
727              
728             %breakpoints = () ;
729              
730             return(1) ;
731             }
732              
733             #----------------------------------------------------------------------
734              
735             sub ListDebuggerSubs
736             {
737              
738             =head2 ListDebuggerSubs
739              
740             List all the debugger subs registered by modules loading Debug::Mixin on STDOUT.
741              
742             =cut
743              
744             my (@packages) = @_ ;
745              
746             unless(@packages)
747             {
748             @packages = keys %debugger_subs ;
749             }
750            
751             for my $package(@packages)
752             {
753             Output(DumpTree($debugger_subs{$package}, "$package:")) ;
754             }
755              
756             return(1) ;
757             }
758              
759             #----------------------------------------------------------------------
760              
761             sub ListBreakpoints
762             {
763              
764             =head2 ListBreakpoints
765              
766             List, on STDOUT, all the breakpoints matching the name regex passed as argument.
767              
768             =cut
769              
770             my ($breakpoint_regex) = @_ ;
771             $breakpoint_regex = qr/./sxm unless defined $breakpoint_regex ;
772              
773             for my $breakpoint_name (sort keys %breakpoints)
774             {
775             if($breakpoint_name =~ $breakpoint_regex)
776             {
777             Output(DumpTree($breakpoints{$breakpoint_name}, "$breakpoint_name:")) ;
778             }
779             }
780              
781             return(1) ;
782             }
783              
784             #----------------------------------------------------------------------
785              
786             sub GetBreakpoints
787             {
788              
789             =head2 GetBreakpoints
790              
791             Returns a reference to all the breakpoints. Elements are returned in the insertion order.
792              
793             Use this only if you know what you are doing.
794              
795             =cut
796              
797             return(\%breakpoints) ;
798             }
799              
800             #----------------------------------------------------------------------
801              
802             sub ActivateBreakpoints
803             {
804              
805             =head2 ActivateBreakpoints
806              
807             Activate all the breakpoints matching the name regex passed as argument.
808              
809             Only active breakpoints are checked by Debug::Mixin.
810              
811             =cut
812              
813             my (@breakpoint_regexes) = @_ ;
814             push @breakpoint_regexes, q{.} unless @breakpoint_regexes ;
815              
816             my $activated_breakpoints = 0 ;
817              
818             for my $breakpoint_name (sort keys %breakpoints)
819             {
820             for my $breakpoint_regex (@breakpoint_regexes)
821             {
822             next unless $breakpoint_name =~ $breakpoint_regex ;
823            
824             $breakpoints{$breakpoint_name}{ACTIVE} = 1 ;
825             carp("Breakpoint '$breakpoint_name' activated. \n") ;#bp local subs
826            
827             $activated_breakpoints++ ;
828             }
829             }
830              
831             return($activated_breakpoints) ;
832             }
833              
834             #----------------------------------------------------------------------
835              
836             sub DeactivateBreakpoints
837             {
838              
839             =head2 DeactivateBreakpoints
840              
841             Deactivate all the breakpoints matching the name regex passed as argument.
842              
843             Only active breakpoints are checked by when you call I.
844              
845             =cut
846              
847             my (@breakpoint_regexes) = @_ ;
848             push @breakpoint_regexes, q{.} unless @breakpoint_regexes ;
849              
850             my $deactivated_breakpoints = 0 ;
851              
852             for my $breakpoint_name (sort keys %breakpoints)
853             {
854             for my $breakpoint_regex (@breakpoint_regexes)
855             {
856             next unless $breakpoint_name =~ $breakpoint_regex ;
857            
858             $breakpoints{$breakpoint_name}{ACTIVE} = 0 ;
859             carp("Breakpoint '$breakpoint_name' deactivated. \n") ;
860             }
861             }
862              
863             return($deactivated_breakpoints) ;
864             }
865              
866             #----------------------------------------------------------------------
867              
868             sub ActivateAlwaysUseDebugger
869             {
870              
871             =head2 ActivateAlwaysUseDebugger
872              
873             Sets all breakpoints matching the name regex passed as argument to always jumps to the perl debugger.
874              
875             =cut
876              
877             my (@breakpoint_regexes) = @_ ;
878             my $always_use_debugger_breakpoints = 0 ;
879              
880             for my $breakpoint_name (sort keys %breakpoints)
881             {
882             for my $breakpoint_regex (@breakpoint_regexes)
883             {
884             next unless $breakpoint_name =~ $breakpoint_regex ;
885            
886             $breakpoints{$breakpoint_name}{ALWAYS_USE_DEBUGGER} = 1 ;
887             carp("Breakpoint '$breakpoint_name' will always activate the perl debugger.\n") ;
888            
889             $always_use_debugger_breakpoints++ ;
890             }
891             }
892              
893             return($always_use_debugger_breakpoints) ;
894             }
895              
896             #----------------------------------------------------------------------
897              
898             sub DeactivateAlwaysUseDebugger
899             {
900              
901             =head2 DeactivateAlwaysUseDebugger
902              
903             Sets all breakpoints matching the name regex passed as argument, to never jumps to the perl debugger.
904              
905             =cut
906              
907             my (@breakpoint_regexes) = @_ ;
908             my $never_use_debugger_breakpoints = 0 ;
909              
910             for my $breakpoint_name (sort keys %breakpoints)
911             {
912             for my $breakpoint_regex (@breakpoint_regexes)
913             {
914             next unless $breakpoint_name =~ $breakpoint_regex ;
915            
916             $breakpoints{$breakpoint_name}{ALWAYS_USE_DEBUGGER} = 0 ;
917             carp("Breakpoint '$breakpoint_name' will NOT always activate the perl debugger. \n") ;
918            
919             $never_use_debugger_breakpoints++ ;
920             }
921             }
922              
923             return($never_use_debugger_breakpoints) ;
924             }
925              
926             #----------------------------------------------------------------------
927              
928             sub CheckBreakpoints ## no critic (Subroutines::RequireArgUnpacking)
929             {
930              
931             =head2 CheckBreakpoints
932              
933             Check a user state against all registered breakpoints. Returned value tells caller if it
934             should jump into the debugger.
935              
936             if(Debug::Mixin::IsDebuggerEnabled())
937             {#bp local subs
938              
939              
940             %debug_data =
941             (
942             # user data passed to the breakpoint actions
943             TYPE => '...'
944             , COMMENT => '...'
945             , ...
946             ) ;
947            
948             $DB::single = 1 if(Debug::Mixin::CheckBreakpoint(%debug_data)) ;
949             }
950              
951              
952             =cut
953            
954             return(0) unless $debug_enabled ;
955              
956             my (%user_state) = @_ ;
957              
958             my $use_debugger = 0 ;
959              
960             my ($package, $file_name, $line) = caller() ;
961              
962             for my $breakpoint (values %breakpoints)
963             {
964             next unless $breakpoint->{ACTIVE} ;
965            
966             my $breakpoint_matches = 0 ;
967            
968             if(exists $breakpoint->{FILTERS})
969             {
970             my $filter_index = 0 ;
971            
972             for my $filter ( @{$breakpoint->{FILTERS}})
973             {
974             eval
975             {
976             $breakpoint_matches+=
977             $filter->
978             (
979             %user_state,
980             DEBUG_MIXIN_BREAKPOINT => $breakpoint,
981             DEBUG_MIXIN_CALLED_AT => {FILE => $file_name,LINE => $line}
982             ) ;
983             } ;
984            
985             if($EVAL_ERROR)
986             {
987             my $original_exception = $EVAL_ERROR ;
988             chomp $original_exception ;
989            
990             my $error_message =
991             "CheckBreakpoints: Caught exception while running breakpoint filter!\n"
992             . DumpTree
993             ({
994             BREAKPOINT => $breakpoint,
995             CALLED_AT => {FILE => $file_name,LINE => $line}
996             })
997             . "Action # $filter_index\n"
998             . "Original exception: '$original_exception'\n";
999            
1000             if(*DB::DB{CODE})
1001             {
1002             carp $error_message ;
1003             $DB::single = 1 ; ## no critic
1004             }
1005             else
1006             {
1007             croak $error_message ;
1008             }
1009             }
1010            
1011             $filter_index++ ;
1012             }
1013             }
1014             else
1015             {
1016             $breakpoint_matches++ ;
1017             }
1018            
1019             $use_debugger++ if $breakpoint->{ALWAYS_USE_DEBUGGER} ;
1020            
1021             if($breakpoint_matches)
1022             {
1023             $breakpoint->{MATCHED}++ ;
1024            
1025             my $action_index = 0 ;
1026             for my $action (@{$breakpoint->{ACTIONS}})
1027             {
1028             eval
1029             {
1030             my $result = $action->
1031             (
1032             %user_state,
1033             DEBUG_MIXIN_BREAKPOINT => $breakpoint,
1034             DEBUG_MIXIN_CALLED_AT => {FILE => $file_name,LINE => $line}
1035             ) ;
1036            
1037             $use_debugger += $result || 0 ;
1038             } ;
1039            
1040             if($EVAL_ERROR)
1041             {
1042             my $original_exception = $EVAL_ERROR ;
1043             chomp $original_exception ;
1044            
1045             my $error_message =
1046             "CheckBreakpoints: Caught exception while running breakpoint action!\n"
1047             . DumpTree
1048             ({
1049             BREAKPOINT => $breakpoint,
1050             CALLED_AT => {FILE => $file_name,LINE => $line}
1051             })
1052             . "Action # $action_index\n"
1053             . "Original exception: '$original_exception'\n";
1054            
1055             if(*DB::DB{CODE})
1056             {
1057             carp $error_message ;
1058             $DB::single = 1 ; ## no critic
1059             }
1060             else
1061             {
1062             croak $error_message ;
1063             }
1064             }
1065            
1066             $action_index++ ;
1067             }
1068            
1069             if(*DB::DB{CODE} && exists $breakpoint->{DEBUGGER_SUBS})
1070             {
1071             HandleBreakpointSubInteraction($breakpoint, $file_name, $line, \%user_state) ;
1072             }
1073             }
1074             }
1075              
1076             return($use_debugger) ;
1077             }
1078              
1079             #-------------------------------------------------------------------------------
1080              
1081             sub HandleBreakpointSubInteraction
1082             {
1083              
1084             =head2 HandleBreakpointSubInteraction
1085              
1086             Private subroutine handling user interaction in a debugger session.
1087              
1088             =cut
1089              
1090             my ($breakpoint, $file_name, $line, $user_state) = @_ ;
1091             my $choice = $EMPTY_STRING ;
1092              
1093             do
1094             {
1095             my $header = "Debug::Mixin: Available subs at breakpoint '$breakpoint->{NAME}' ($breakpoint->{MATCHED}):" ;
1096             my $separator = q{-} x length $header ;
1097             Output("$separator\n$header\n$separator\n") ;
1098              
1099             my $index = 0 ;
1100              
1101             my $max_length = 0 ;
1102             for my $sub (@{$breakpoint->{DEBUGGER_SUBS}})
1103             {
1104             $max_length = length($sub->{NAME}) if length($sub->{NAME}) > $max_length ;
1105             }
1106              
1107             for my $sub (@{$breakpoint->{DEBUGGER_SUBS}})
1108             {
1109             Output(sprintf(" #%2d %${max_length}s => $sub->{DESCRIPTION}\n", $index, $sub->{NAME})) ;
1110             $index++ ;
1111             }
1112            
1113             Output("\n'#' to run sub, 'd #' for a long descriptions of the sub or 'c' to continue.\n") ;
1114             Output(q{>}) ;
1115            
1116             $choice = <> ;
1117             chomp($choice) ;
1118            
1119             for($choice)
1120             {
1121             /^[0-9]+$/smx and do
1122             {
1123             if($choice < @{$breakpoint->{DEBUGGER_SUBS}})
1124             {
1125             $breakpoint->{DEBUGGER_SUBS}[$choice]{SUB}->
1126             (
1127             %{$user_state},
1128             DEBUG_MIXIN_BREAKPOINT => $breakpoint,
1129             DEBUG_MIXIN_CALLED_AT => {FILE => $file_name,LINE => $line}
1130             ) ;
1131             }
1132             #else
1133             # invalid input re-query user
1134              
1135             last ;
1136             } ;
1137            
1138             /^d ([0-9]+)$/smx and do
1139             {
1140             my $sub_index = $1 ; ## no critic
1141            
1142             if($sub_index < @{$breakpoint->{DEBUGGER_SUBS}})
1143             {
1144             my $sub = $breakpoint->{DEBUGGER_SUBS}[$sub_index] ;
1145            
1146             my $sub_header = "$sub->{NAME}:" ;
1147             my $sub_separator = q{-} x length($sub_header) ;
1148            
1149             Output("$sub_header\n$sub_separator\n$sub->{HELP}\n\n") ;
1150             }
1151            
1152             last ;
1153             }
1154             }
1155             }
1156             while($choice ne 'c') ;
1157              
1158             return(1) ;
1159             }
1160              
1161             #-------------------------------------------------------------------------------
1162              
1163             sub dm_help
1164             {
1165              
1166             =head2 dm_help
1167              
1168             Displays the commands made available by B in the debugger.
1169              
1170             =cut
1171              
1172             Output(<<'EOC') ;
1173             dm_subs list and run debugging subs
1174            
1175             dm_load @files load breakpoints files
1176              
1177             # all breakpoints functions take a optional regex
1178            
1179             dm_bp list breakpoints
1180             dm_activate activate breakpoints
1181             dm_deactivate deactivate breakpoints
1182             dm_use_debugger jump in debugger
1183             dm_dont_use_debugger jump in debugger only if a breakpoint action says to
1184              
1185             EOC
1186              
1187             return(1) ;
1188             }
1189              
1190             #-------------------------------------------------------------------------------
1191              
1192             sub dm_subs
1193             {
1194              
1195             =head2 dm_subs
1196              
1197             List all the available debugging subs and interacts with the user to run them.
1198              
1199             =cut
1200              
1201             my $choice = $EMPTY_STRING ;
1202              
1203             do
1204             {
1205             my $header = 'Debug::Mixin: Available subs:' ;
1206             my $separator = q{-} x length $header ;
1207             Output("$separator\n$header\n$separator\n") ;
1208            
1209             my $index = 0 ;
1210             my $max_length = 0 ;
1211             my @subs = () ;
1212            
1213             for my $package (keys %debugger_subs)
1214             {
1215             Output("$package:\n") ;
1216            
1217             for my $sub (values %{$debugger_subs{$package}})
1218             {
1219             push @subs, $sub ;
1220            
1221             Output(sprintf(" #%2d $sub->{NAME} => $sub->{DESCRIPTION}\n", $index)) ;
1222             $index++ ;
1223             }
1224            
1225             Output("\n") ;
1226             }
1227            
1228             Output("\n'#' to run sub, 'd #' for a long descriptions of the sub or 'c' to continue.\n") ;
1229             Output(q{>}) ;
1230            
1231             $choice = <> ;
1232             chomp($choice) ;
1233            
1234             for($choice)
1235             {
1236             /^[0-9]+$/smx and do
1237             {
1238             if($choice < @subs)
1239             {
1240             $subs[$choice]{SUB}->() ;
1241             }
1242             #else
1243             # invalid input re-query user
1244            
1245             last ;
1246             } ;
1247            
1248             /^d ([0-9]+)$/smx and do
1249             {
1250             my $sub_index = $1 ; ## no critic
1251            
1252             if($sub_index < @subs)
1253             {
1254             my $sub = $subs[$sub_index] ;
1255            
1256             my $sub_header = "$sub->{NAME}:" ;
1257             my $sub_separator = q{-} x length($sub_header) ;
1258            
1259             Output("$sub_header\n$sub_separator\n$sub->{HELP}\n\n") ;
1260             }
1261            
1262             last ;
1263             } ;
1264            
1265             }
1266             }
1267             while($choice ne 'c') ;
1268              
1269             return(1) ;
1270             }
1271              
1272             #-------------------------------------------------------------------------------
1273              
1274             sub Output ## no critic (Subroutines::RequireArgUnpacking)
1275             {
1276              
1277             =head2 Output
1278              
1279             Prints the passed arguments
1280              
1281             =cut
1282              
1283             print(@_) or die "Can't output!\n" ;
1284              
1285             return ;
1286             }
1287              
1288             #-------------------------------------------------------------------------------
1289             1 ;
1290              
1291             =head1 TO DO
1292              
1293             More test, testing the module through the perl debugger's automation.
1294              
1295             =head1 BUGS AND LIMITATIONS
1296              
1297             None so far.
1298              
1299             =head1 AUTHOR
1300              
1301             Khemir Nadim ibn Hamouda
1302             CPAN ID: NKH
1303             mailto:nadim@khemir.net
1304              
1305             =head1 LICENSE AND COPYRIGHT
1306              
1307             This program is free software; you can redistribute
1308             it and/or modify it under the same terms as Perl itself.
1309              
1310             =head1 SUPPORT
1311              
1312             You can find documentation for this module with the perldoc command.
1313              
1314             perldoc Debug::Mixin
1315              
1316             You can also look for information at:
1317              
1318             =over 4
1319              
1320             =item * AnnoCPAN: Annotated CPAN documentation
1321              
1322             L
1323              
1324             =item * RT: CPAN's request tracker
1325              
1326             Please report any bugs or feature requests to L .
1327              
1328             We will be notified, and then you'll automatically be notified of progress on
1329             your bug as we make changes.
1330              
1331             =item * Search CPAN
1332              
1333             L
1334              
1335             =back
1336              
1337             =head1 SEE ALSO
1338              
1339             L
1340              
1341             =cut