The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

package VCP::Debug ;
=head1 NAME
VCP::Debug - debugging support for VCP
=head1 SYNOPSIS
=head1 DESCRIPTION
=head1 EXPORTS
The following functions may be exported: L</debug>, L</enable_debug>,
L</debugging>
L</disable_debug>, along with the tags ':all' and ':debug'. Use the latter
to head off future namespace pollution in case :all gets expanded in the
future..
A warning will be emitted on program exit for any specs that aren't used,
to help you make sure that you are using sensible specs.
=over
=cut
use strict ;
use vars qw( $VERSION @ISA @EXPORT_OK %EXPORT_TAGS ) ;
use Exporter ;
@ISA = qw( Exporter ) ;
@EXPORT_OK = qw(
debug
enable_debug
disable_debug
debugging
explicitly_debugging
) ;
%EXPORT_TAGS = (
'all' => \@EXPORT_OK,
'debug' => \@EXPORT_OK,
) ;
$VERSION = 0.1 ;
# TODO:
#=item use
#=item import
#
#In addition to all of the routines and tags that C<use> and C<import> normally
#take (see above), you may also pass in pairwise debugging definitions like
#so:
#
# use VCP::debug (
# ":all",
# DEBUGGING_FOO => "foo,bar",
# ) ;
#
#Any all caps export import requests are created as subroutines that may well be
#optimized away at compile time if "enable_debugging" has not been called. This
#requires a conspiracy between the author of a module and the author of the main
#program to call enable_debugging I<before> C<use>ing any modules that leverage
#this feature, otherwise compile-time optimizations won't occur.
#
=item debug
debug $foo if debugging $self ;
Emits a line of debugging (a "\n" will be appended). Use debug_some
to avoid the "\n". Any undefined parameters will be displayed as
C<E<lt>undefE<gt>>.
=cut
my $dump_undebugged ;
my $reported_specs ;
my @debug_specs ;
my %used_specs ;
my %debugging ;
END {
$used_specs{'##NEVER_MATCH##'} = 1 ;
my @unused = grep ! $used_specs{$_}, @debug_specs ;
warn "vcp: Unused debug specs: ", join( ', ', map "/$_/", @unused ), "\n"
if @unused ;
if ( @unused || $dump_undebugged ) {
my @undebugged = grep {
my $name = $_ ;
! grep $name =~ /$_/i, keys %used_specs
} map lc $_, sort keys %debugging ;
if ( @undebugged ) {
warn "vcp: Undebugged things: ", join( ', ', @undebugged ), "\n" ;
}
else {
warn "vcp: No undebugged things\n" ;
}
}
}
sub debug {
return unless @debug_specs ;
if ( @_ ) {
my $t = join( '', map defined $_ ? $_ : "<undef>", @_ ) ;
if ( length $t ) {
print STDERR $t, substr( $t, -1 ) eq "\n" ? () : "\n" ;
}
}
}
sub debug_some {
return unless @debug_specs ;
print STDERR map defined $_ ? $_ : "<undef>", @_ if @_ ;
}
=item debugging
debug "blah" if debugging ;
Returns TRUE if the caller's module is being debugged
debug "blah" if debugging $self ;
debug "blah" if debugging $other, $self ; ## ORs the arguments together
Returns TRUE if any of the arguments are being debugged. Plain
strings can be passed or blessed references.
=cut
sub _report_specs {
my @report = grep ! /##NEVER_MATCH##/, @debug_specs ;
print STDERR "Debugging ",join( ', ', map "/$_/", @report ),"\n"
if @report ;
$reported_specs = 1 ;
}
sub debugging {
return undef unless @debug_specs ;
my $result ;
my @missed ;
for my $where ( @_ ? map ref $_ || $_, @_ : scalar caller ) {
if ( ! exists $debugging{$where} ) {
# print STDERR "missed $where\n" ;
## If this is the first miss, then these may not have been reported.
_report_specs unless $reported_specs ;
## We go ahead and evaluate all specs instead of returning when the
## first is found so that we can set $used_specs for all specs that
## match.
$debugging{$where} = 0 ;
for my $spec ( @debug_specs ) {
next if $spec eq '##NEVER_MATCH##' ;
# print STDERR " /$spec/:\n" ;
if ( $where =~ /$spec/i ) {
$debugging{$where} = 1 ;
$used_specs{$spec} = 1 ;
$result = 1 ;
## no last: we want to build up %used_specs. There
## aren't usually many specs anyway.
}
else {
# print STDERR " ! /$spec/\n" ;
}
}
}
# print STDERR "$where ", $debugging{$where} ? 'yes' : 'no', "\n" ;
return 1 if $debugging{$where} ;
}
return $result ;
}
=item explicitly_debugging
debug "blah" if explicitly_debugging ;
Returns TRUE if the caller's module is being debugged by a literal match
instead of a pattern match. This is used when debugging output would normally
be congested with too much crap from a particular subsystem when using a
wildcard debug spec (like ".*"), but you want the ability to turn on debugging
for that subsystem:
debug "blah" if explicitly_debugging "VCP::Dest::sort" ;
requires an explicit C<VCP::Dest::sort> to be given in the debug specs.
debug "blah" if explicitly_debugging $self ;
debug "blah" if explicitly_debugging $other, $self ; ## ORs the args
Returns TRUE if any of the arguments are being debugged. Plain
strings can be passed or blessed references.
=cut
my %explicitly_debugging ;
sub explicitly_debugging {
return undef unless @debug_specs ;
my $result ;
my @missed ;
for my $where ( @_ ? map ref $_ || $_, @_ : scalar caller ) {
if ( ! exists $explicitly_debugging{$where} ) {
# print STDERR "missed $where\n" ;
## If this is the first miss, then these may not have been reported.
_report_specs unless $reported_specs ;
## We go ahead and evaluate all specs instead of returning when the
## first is found so that we can set $used_specs for all specs that
## match.
$explicitly_debugging{$where} = 0 ;
for my $spec ( @debug_specs ) {
next if $spec eq '##NEVER_MATCH##' ;
# print STDERR " /$spec/:\n" ;
if ( lc $where eq lc $spec ) {
$explicitly_debugging{$where} = 1 ;
$used_specs{$spec} = 1 ;
$result = 1 ;
## no last: we want to build up %used_specs. There
## aren't usually many specs anyway.
}
else {
# print STDERR " ! /$spec/\n" ;
}
}
}
# print STDERR "$where ", $debugging{$where} ? 'yes' : 'no', "\n" ;
return 1 if $explicitly_debugging{$where} ;
}
return $result ;
}
=item disable_debug
Disable all debugging.
=cut
sub disable_debug() {
@debug_specs = () ;
return ;
}
=item enable_debug
enable_debug ;
enable_debug( ...debug specs... ) ;
A debug spec is a regular expression that matches the name of a module.
=cut
sub enable_debug {
my %specs = map { ( $_ => 1 ) } @debug_specs, @_ ;
my @new_debug_specs = %specs
? keys %specs
: qr/^/ ;
_report_specs
if $reported_specs && @debug_specs != @new_debug_specs ;
@debug_specs = map(
/^what$/i && ( $dump_undebugged = 1 ) ? '##NEVER_MATCH##' : $_,
@new_debug_specs
) ;
return ;
}
=head1 COPYRIGHT
Copyright 2000, Perforce Software, Inc. All Rights Reserved.
This module and the VCP package are licensed according to the terms given in
the file LICENSE accompanying this distribution, a copy of which is included in
L<vcp>.
=head1 AUTHOR
Barrie Slaymaker <barries@slaysys.com>
=cut
1