This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Some source files and documentation files need not to be executable
[perl5.git] / lib / diagnostics.pm
CommitLineData
4633a7c4 1package diagnostics;
4633a7c4
LW
2
3=head1 NAME
4
c7bcd97d 5diagnostics, splain - produce verbose warning diagnostics
4633a7c4
LW
6
7=head1 SYNOPSIS
8
c7bcd97d 9Using the C<diagnostics> pragma:
4633a7c4
LW
10
11 use diagnostics;
12 use diagnostics -verbose;
13
14 enable diagnostics;
15 disable diagnostics;
16
c7bcd97d 17Using the C<splain> standalone filter program:
4633a7c4
LW
18
19 perl program 2>diag.out
20 splain [-v] [-p] diag.out
21
58618f23
FD
22Using diagnostics to get stack traces from a misbehaving script:
23
24 perl -Mdiagnostics=-traceonly my_script.pl
25
4633a7c4
LW
26=head1 DESCRIPTION
27
28=head2 The C<diagnostics> Pragma
29
30This module extends the terse diagnostics normally emitted by both the
c411974d
SP
31perl compiler and the perl interpreter (from running perl with a -w
32switch or C<use warnings>), augmenting them with the more
4633a7c4 33explicative and endearing descriptions found in L<perldiag>. Like the
1fef88e7 34other pragmata, it affects the compilation phase of your program rather
4633a7c4
LW
35than merely the execution phase.
36
37To use in your program as a pragma, merely invoke
38
39 use diagnostics;
40
41at the start (or near the start) of your program. (Note
42that this I<does> enable perl's B<-w> flag.) Your whole
43compilation will then be subject(ed :-) to the enhanced diagnostics.
44These still go out B<STDERR>.
45
ae2c041d 46Due to the interaction between runtime and compiletime issues,
4633a7c4 47and because it's probably not a very good idea anyway,
ae2c041d 48you may not use C<no diagnostics> to turn them off at compiletime.
3d0ae7ba 49However, you may control their behaviour at runtime using the
4633a7c4
LW
50disable() and enable() methods to turn them off and on respectively.
51
52The B<-verbose> flag first prints out the L<perldiag> introduction before
1fef88e7
JM
53any other diagnostics. The $diagnostics::PRETTY variable can generate nicer
54escape sequences for pagers.
4633a7c4 55
097b73fc
BB
56Warnings dispatched from perl itself (or more accurately, those that match
57descriptions found in L<perldiag>) are only displayed once (no duplicate
49704364 58descriptions). User code generated warnings a la warn() are unaffected,
097b73fc
BB
59allowing duplicate user messages to be displayed.
60
58618f23
FD
61This module also adds a stack trace to the error message when perl dies.
62This is useful for pinpointing what caused the death. The B<-traceonly> (or
3c4b39be 63just B<-t>) flag turns off the explanations of warning messages leaving just
58618f23
FD
64the stack traces. So if your script is dieing, run it again with
65
66 perl -Mdiagnostics=-traceonly my_bad_script
67
68to see the call stack at the time of death. By supplying the B<-warntrace>
69(or just B<-w>) flag, any warnings emitted will also come with a stack
70trace.
71
4633a7c4
LW
72=head2 The I<splain> Program
73
74While apparently a whole nuther program, I<splain> is actually nothing
75more than a link to the (executable) F<diagnostics.pm> module, as well as
76a link to the F<diagnostics.pod> documentation. The B<-v> flag is like
77the C<use diagnostics -verbose> directive.
78The B<-p> flag is like the
79$diagnostics::PRETTY variable. Since you're post-processing with
80I<splain>, there's no sense in being able to enable() or disable() processing.
81
82Output from I<splain> is directed to B<STDOUT>, unlike the pragma.
83
84=head1 EXAMPLES
85
86The following file is certain to trigger a few errors at both
ae2c041d 87runtime and compiletime:
4633a7c4
LW
88
89 use diagnostics;
90 print NOWHERE "nothing\n";
91 print STDERR "\n\tThis message should be unadorned.\n";
92 warn "\tThis is a user warning";
93 print "\nDIAGNOSTIC TESTER: Please enter a <CR> here: ";
94 my $a, $b = scalar <STDIN>;
95 print "\n";
96 print $x/$y;
97
98If you prefer to run your program first and look at its problem
99afterwards, do this:
100
101 perl -w test.pl 2>test.out
102 ./splain < test.out
103
104Note that this is not in general possible in shells of more dubious heritage,
1fef88e7 105as the theoretical
4633a7c4
LW
106
107 (perl -w test.pl >/dev/tty) >& test.out
108 ./splain < test.out
109
110Because you just moved the existing B<stdout> to somewhere else.
111
112If you don't want to modify your source code, but still have on-the-fly
113warnings, do this:
114
115 exec 3>&1; perl -w test.pl 2>&1 1>&3 3>&- | splain 1>&2 3>&-
116
117Nifty, eh?
118
119If you want to control warnings on the fly, do something like this.
120Make sure you do the C<use> first, or you won't be able to get
121at the enable() or disable() methods.
122
123 use diagnostics; # checks entire compilation phase
124 print "\ntime for 1st bogus diags: SQUAWKINGS\n";
125 print BOGUS1 'nada';
126 print "done with 1st bogus\n";
127
128 disable diagnostics; # only turns off runtime warnings
129 print "\ntime for 2nd bogus: (squelched)\n";
130 print BOGUS2 'nada';
131 print "done with 2nd bogus\n";
132
133 enable diagnostics; # turns back on runtime warnings
134 print "\ntime for 3rd bogus: SQUAWKINGS\n";
135 print BOGUS3 'nada';
136 print "done with 3rd bogus\n";
137
138 disable diagnostics;
139 print "\ntime for 4th bogus: (squelched)\n";
140 print BOGUS4 'nada';
141 print "done with 4th bogus\n";
142
143=head1 INTERNALS
144
145Diagnostic messages derive from the F<perldiag.pod> file when available at
146runtime. Otherwise, they may be embedded in the file itself when the
147splain package is built. See the F<Makefile> for details.
148
149If an extant $SIG{__WARN__} handler is discovered, it will continue
1fef88e7 150to be honored, but only after the diagnostics::splainthis() function
4633a7c4
LW
151(the module's $SIG{__WARN__} interceptor) has had its way with your
152warnings.
153
154There is a $diagnostics::DEBUG variable you may set if you're desperately
155curious what sorts of things are being intercepted.
156
157 BEGIN { $diagnostics::DEBUG = 1 }
158
159
160=head1 BUGS
161
162Not being able to say "no diagnostics" is annoying, but may not be
163insurmountable.
164
165The C<-pretty> directive is called too late to affect matters.
864e1151 166You have to do this instead, and I<before> you load the module.
4633a7c4
LW
167
168 BEGIN { $diagnostics::PRETTY = 1 }
169
170I could start up faster by delaying compilation until it should be
a6006777
PP
171needed, but this gets a "panic: top_level" when using the pragma form
172in Perl 5.001e.
4633a7c4
LW
173
174While it's true that this documentation is somewhat subserious, if you use
175a program named I<splain>, you should expect a bit of whimsy.
176
177=head1 AUTHOR
178
352854fa 179Tom Christiansen <F<tchrist@mox.perl.com>>, 25 June 1995.
4633a7c4
LW
180
181=cut
182
7a4340ed 183use strict;
d923656e 184use 5.009001;
5f05dabc 185use Carp;
58618f23 186$Carp::Internal{__PACKAGE__.""}++;
5f05dabc 187
d923656e 188our $VERSION = 1.17;
7a4340ed
GS
189our $DEBUG;
190our $VERBOSE;
191our $PRETTY;
58618f23
FD
192our $TRACEONLY = 0;
193our $WARNTRACE = 0;
1e4e2d84 194
5f05dabc 195use Config;
7a4340ed 196my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)};
5f05dabc 197if ($^O eq 'VMS') {
91a06757
CS
198 require VMS::Filespec;
199 $privlib = VMS::Filespec::unixify($privlib);
200 $archlib = VMS::Filespec::unixify($archlib);
5f05dabc 201}
7a4340ed 202my @trypod = (
7ec2cea4 203 "$archlib/pod/perldiag.pod",
0ff3fa1a 204 "$privlib/pod/perldiag-$Config{version}.pod",
5459498c 205 "$privlib/pod/perldiag.pod",
7ec2cea4 206 "$archlib/pods/perldiag.pod",
0ff3fa1a 207 "$privlib/pods/perldiag-$Config{version}.pod",
5459498c 208 "$privlib/pods/perldiag.pod",
7ec2cea4 209 );
fb73857a
PP
210# handy for development testing of new warnings etc
211unshift @trypod, "./pod/perldiag.pod" if -e "pod/perldiag.pod";
7a4340ed 212(my $PODFILE) = ((grep { -e } @trypod), $trypod[$#trypod])[0];
5f05dabc 213
95e8664e
CN
214if ($^O eq 'MacOS') {
215 # just updir one from each lib dir, we'll find it ...
216 ($PODFILE) = grep { -e } map { "$_:pod:perldiag.pod" } @INC;
217}
218
219
4633a7c4
LW
220$DEBUG ||= 0;
221my $WHOAMI = ref bless []; # nobody's business, prolly not even mine
222
7a4340ed 223local $| = 1;
d923656e 224my $_;
4633a7c4 225
7a4340ed
GS
226my $standalone;
227my(%HTML_2_Troff, %HTML_2_Latin_1, %HTML_2_ASCII_7);
228
4633a7c4 229CONFIG: {
7a4340ed 230 our $opt_p = our $opt_d = our $opt_v = our $opt_f = '';
4633a7c4 231
7a4340ed 232 unless (caller) {
4633a7c4
LW
233 $standalone++;
234 require Getopt::Std;
91a06757
CS
235 Getopt::Std::getopts('pdvf:')
236 or die "Usage: $0 [-v] [-p] [-f splainpod]";
4633a7c4
LW
237 $PODFILE = $opt_f if $opt_f;
238 $DEBUG = 2 if $opt_d;
239 $VERBOSE = $opt_v;
240 $PRETTY = $opt_p;
7a4340ed 241 }
4633a7c4
LW
242
243 if (open(POD_DIAG, $PODFILE)) {
244 warn "Happy happy podfile from real $PODFILE\n" if $DEBUG;
245 last CONFIG;
246 }
247
248 if (caller) {
249 INCPATH: {
7a4340ed 250 for my $file ( (map { "$_/$WHOAMI.pm" } @INC), $0) {
4633a7c4
LW
251 warn "Checking $file\n" if $DEBUG;
252 if (open(POD_DIAG, $file)) {
253 while (<POD_DIAG>) {
7a4340ed
GS
254 next unless
255 /^__END__\s*# wish diag dbase were more accessible/;
4633a7c4
LW
256 print STDERR "podfile is $file\n" if $DEBUG;
257 last INCPATH;
258 }
259 }
260 }
261 }
262 } else {
263 print STDERR "podfile is <DATA>\n" if $DEBUG;
264 *POD_DIAG = *main::DATA;
265 }
266}
267if (eof(POD_DIAG)) {
268 die "couldn't find diagnostic data in $PODFILE @INC $0";
269}
270
271
272%HTML_2_Troff = (
273 'amp' => '&', # ampersand
274 'lt' => '<', # left chevron, less-than
275 'gt' => '>', # right chevron, greater-than
276 'quot' => '"', # double quote
277
278 "Aacute" => "A\\*'", # capital A, acute accent
279 # etc
280
281);
282
283%HTML_2_Latin_1 = (
284 'amp' => '&', # ampersand
285 'lt' => '<', # left chevron, less-than
286 'gt' => '>', # right chevron, greater-than
287 'quot' => '"', # double quote
288
289 "Aacute" => "\xC1" # capital A, acute accent
290
291 # etc
292);
293
294%HTML_2_ASCII_7 = (
295 'amp' => '&', # ampersand
296 'lt' => '<', # left chevron, less-than
297 'gt' => '>', # right chevron, greater-than
298 'quot' => '"', # double quote
299
300 "Aacute" => "A" # capital A, acute accent
301 # etc
302);
303
7a4340ed 304our %HTML_Escapes;
4633a7c4
LW
305*HTML_Escapes = do {
306 if ($standalone) {
307 $PRETTY ? \%HTML_2_Latin_1 : \%HTML_2_ASCII_7;
308 } else {
309 \%HTML_2_Latin_1;
310 }
311};
312
313*THITHER = $standalone ? *STDOUT : *STDERR;
314
49704364 315my %transfmt = ();
7a4340ed 316my $transmo = <<EOFUNC;
4633a7c4 317sub transmo {
599cee73 318 #local \$^W = 0; # recursive warnings we do NOT need!
4633a7c4
LW
319 study;
320EOFUNC
321
7a4340ed
GS
322my %msg;
323{
4633a7c4 324 print STDERR "FINISHING COMPILATION for $_\n" if $DEBUG;
7a4340ed 325 local $/ = '';
7a4340ed
GS
326 my $header;
327 my $for_item;
4633a7c4 328 while (<POD_DIAG>) {
4633a7c4
LW
329
330 unescape();
331 if ($PRETTY) {
332 sub noop { return $_[0] } # spensive for a noop
333 sub bold { my $str =$_[0]; $str =~ s/(.)/$1\b$1/g; return $str; }
334 sub italic { my $str = $_[0]; $str =~ s/(.)/_\b$1/g; return $str; }
67612b68 335 s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/bold($+)/ges;
4633a7c4
LW
336 s/[LIF]<(.*?)>/italic($1)/ges;
337 } else {
67612b68 338 s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/$+/gs;
4633a7c4
LW
339 s/[LIF]<(.*?)>/$1/gs;
340 }
341 unless (/^=/) {
342 if (defined $header) {
343 if ( $header eq 'DESCRIPTION' &&
344 ( /Optional warnings are enabled/
345 || /Some of these messages are generic./
346 ) )
347 {
348 next;
49704364 349 }
4633a7c4
LW
350 s/^/ /gm;
351 $msg{$header} .= $_;
7a4340ed 352 undef $for_item;
4633a7c4
LW
353 }
354 next;
355 }
7a4340ed 356 unless ( s/=item (.*?)\s*\z//) {
4633a7c4
LW
357
358 if ( s/=head1\sDESCRIPTION//) {
359 $msg{$header = 'DESCRIPTION'} = '';
7a4340ed 360 undef $for_item;
4633a7c4 361 }
7a4340ed
GS
362 elsif( s/^=for\s+diagnostics\s*\n(.*?)\s*\z// ) {
363 $for_item = $1;
364 }
4633a7c4
LW
365 next;
366 }
4fdae800 367
5cd5c422
RB
368 if( $for_item ) { $header = $for_item; undef $for_item }
369 else {
370 $header = $1;
371 while( $header =~ /[;,]\z/ ) {
372 <POD_DIAG> =~ /^\s*(.*?)\s*\z/;
373 $header .= ' '.$1;
374 }
375 }
376
49704364 377 # strip formatting directives from =item line
7a4340ed 378 $header =~ s/[A-Z]<(.*?)>/$1/g;
4633a7c4 379
49704364
LW
380 my @toks = split( /(%l?[dx]|%c|%(?:\.\d+)?s)/, $header );
381 if (@toks > 1) {
382 my $conlen = 0;
383 for my $i (0..$#toks){
384 if( $i % 2 ){
385 if( $toks[$i] eq '%c' ){
386 $toks[$i] = '.';
387 } elsif( $toks[$i] eq '%d' ){
388 $toks[$i] = '\d+';
389 } elsif( $toks[$i] eq '%s' ){
390 $toks[$i] = $i == $#toks ? '.*' : '.*?';
391 } elsif( $toks[$i] =~ '%.(\d+)s' ){
392 $toks[$i] = ".{$1}";
393 } elsif( $toks[$i] =~ '^%l*x$' ){
394 $toks[$i] = '[\da-f]+';
395 }
396 } elsif( length( $toks[$i] ) ){
8ec7363a 397 $toks[$i] = quotemeta $toks[$i];
49704364
LW
398 $conlen += length( $toks[$i] );
399 }
400 }
401 my $lhs = join( '', @toks );
402 $transfmt{$header}{pat} =
403 " s{^$lhs}\n {\Q$header\E}s\n\t&& return 1;\n";
404 $transfmt{$header}{len} = $conlen;
4633a7c4 405 } else {
49704364
LW
406 $transfmt{$header}{pat} =
407 " m{^\Q$header\E} && return 1;\n";
408 $transfmt{$header}{len} = length( $header );
4633a7c4
LW
409 }
410
eff9c6e2
CS
411 print STDERR "$WHOAMI: Duplicate entry: \"$header\"\n"
412 if $msg{$header};
4633a7c4
LW
413
414 $msg{$header} = '';
415 }
416
417
418 close POD_DIAG unless *main::DATA eq *POD_DIAG;
419
420 die "No diagnostics?" unless %msg;
421
49704364
LW
422 # Apply patterns in order of decreasing sum of lengths of fixed parts
423 # Seems the best way of hitting the right one.
424 for my $hdr ( sort { $transfmt{$b}{len} <=> $transfmt{$a}{len} }
425 keys %transfmt ){
426 $transmo .= $transfmt{$hdr}{pat};
427 }
4633a7c4
LW
428 $transmo .= " return 0;\n}\n";
429 print STDERR $transmo if $DEBUG;
430 eval $transmo;
431 die $@ if $@;
7a4340ed 432}
4633a7c4
LW
433
434if ($standalone) {
435 if (!@ARGV and -t STDIN) { print STDERR "$0: Reading from STDIN\n" }
7a4340ed 436 while (defined (my $error = <>)) {
4633a7c4
LW
437 splainthis($error) || print THITHER $error;
438 }
439 exit;
7a4340ed
GS
440}
441
442my $olddie;
443my $oldwarn;
4633a7c4
LW
444
445sub import {
446 shift;
7a4340ed
GS
447 $^W = 1; # yup, clobbered the global variable;
448 # tough, if you want diags, you want diags.
0dc02ca5 449 return if defined $SIG{__WARN__} && ($SIG{__WARN__} eq \&warn_trap);
4633a7c4
LW
450
451 for (@_) {
452
453 /^-d(ebug)?$/ && do {
454 $DEBUG++;
455 next;
456 };
457
458 /^-v(erbose)?$/ && do {
459 $VERBOSE++;
460 next;
461 };
462
463 /^-p(retty)?$/ && do {
464 print STDERR "$0: I'm afraid it's too late for prettiness.\n";
465 $PRETTY++;
466 next;
467 };
b4e8c6dd
FD
468 # matches trace and traceonly for legacy doc mixup reasons
469 /^-t(race(only)?)?$/ && do {
58618f23
FD
470 $TRACEONLY++;
471 next;
472 };
b4e8c6dd 473 /^-w(arntrace)?$/ && do {
58618f23
FD
474 $WARNTRACE++;
475 next;
476 };
477
4633a7c4
LW
478 warn "Unknown flag: $_";
479 }
480
481 $oldwarn = $SIG{__WARN__};
482 $olddie = $SIG{__DIE__};
483 $SIG{__WARN__} = \&warn_trap;
484 $SIG{__DIE__} = \&death_trap;
485}
486
487sub enable { &import }
488
489sub disable {
490 shift;
4633a7c4 491 return unless $SIG{__WARN__} eq \&warn_trap;
3d0ae7ba
GS
492 $SIG{__WARN__} = $oldwarn || '';
493 $SIG{__DIE__} = $olddie || '';
4633a7c4
LW
494}
495
496sub warn_trap {
497 my $warning = $_[0];
498 if (caller eq $WHOAMI or !splainthis($warning)) {
58618f23
FD
499 if ($WARNTRACE) {
500 print STDERR Carp::longmess($warning);
501 } else {
502 print STDERR $warning;
503 }
4633a7c4 504 }
58618f23 505 goto &$oldwarn if defined $oldwarn and $oldwarn and $oldwarn ne \&warn_trap;
4633a7c4
LW
506};
507
508sub death_trap {
509 my $exception = $_[0];
55497cff
PP
510
511 # See if we are coming from anywhere within an eval. If so we don't
512 # want to explain the exception because it's going to get caught.
513 my $in_eval = 0;
514 my $i = 0;
58618f23 515 while (my $caller = (caller($i++))[3]) {
55497cff
PP
516 if ($caller eq '(eval)') {
517 $in_eval = 1;
518 last;
519 }
520 }
521
522 splainthis($exception) unless $in_eval;
4633a7c4 523 if (caller eq $WHOAMI) { print STDERR "INTERNAL EXCEPTION: $exception"; }
37120919 524 &$olddie if defined $olddie and $olddie and $olddie ne \&death_trap;
55497cff 525
d23f0205
MS
526 return if $in_eval;
527
55497cff 528 # We don't want to unset these if we're coming from an eval because
d23f0205
MS
529 # then we've turned off diagnostics.
530
531 # Switch off our die/warn handlers so we don't wind up in our own
532 # traps.
533 $SIG{__DIE__} = $SIG{__WARN__} = '';
534
535 # Have carp skip over death_trap() when showing the stack trace.
6f48387a 536 local($Carp::CarpLevel) = 1;
d23f0205 537
6f48387a 538 confess "Uncaught exception from user code:\n\t$exception";
4633a7c4
LW
539 # up we go; where we stop, nobody knows, but i think we die now
540 # but i'm deeply afraid of the &$olddie guy reraising and us getting
541 # into an indirect recursion loop
542};
543
7a4340ed
GS
544my %exact_duplicate;
545my %old_diag;
546my $count;
547my $wantspace;
4633a7c4 548sub splainthis {
58618f23 549 return 0 if $TRACEONLY;
d923656e 550 $_ = shift;
5025c45a 551 local $\;
2a6a970f 552 local $!;
4633a7c4
LW
553 ### &finish_compilation unless %msg;
554 s/\.?\n+$//;
555 my $orig = $_;
556 # return unless defined;
49704364
LW
557
558 # get rid of the where-are-we-in-input part
4633a7c4 559 s/, <.*?> (?:line|chunk).*$//;
49704364
LW
560
561 # Discard 1st " at <file> line <no>" and all text beyond
562 # but be aware of messsages containing " at this-or-that"
563 my $real = 0;
564 my @secs = split( / at / );
18d238e4 565 return unless @secs;
49704364
LW
566 $_ = $secs[0];
567 for my $i ( 1..$#secs ){
568 if( $secs[$i] =~ /.+? (?:line|chunk) \d+/ ){
569 $real = 1;
570 last;
571 } else {
572 $_ .= ' at ' . $secs[$i];
573 }
574 }
575
576 # remove parenthesis occurring at the end of some messages
4633a7c4 577 s/^\((.*)\)$/$1/;
49704364 578
097b73fc
BB
579 if ($exact_duplicate{$orig}++) {
580 return &transmo;
49704364 581 } else {
097b73fc
BB
582 return 0 unless &transmo;
583 }
49704364 584
4633a7c4
LW
585 $orig = shorten($orig);
586 if ($old_diag{$_}) {
587 autodescribe();
588 print THITHER "$orig (#$old_diag{$_})\n";
589 $wantspace = 1;
590 } else {
591 autodescribe();
592 $old_diag{$_} = ++$count;
593 print THITHER "\n" if $wantspace;
594 $wantspace = 0;
595 print THITHER "$orig (#$old_diag{$_})\n";
596 if ($msg{$_}) {
597 print THITHER $msg{$_};
598 } else {
599 if (0 and $standalone) {
600 print THITHER " **** Error #$old_diag{$_} ",
601 ($real ? "is" : "appears to be"),
602 " an unknown diagnostic message.\n\n";
603 }
604 return 0;
605 }
606 }
607 return 1;
608}
609
610sub autodescribe {
611 if ($VERBOSE and not $count) {
612 print THITHER &{$PRETTY ? \&bold : \&noop}("DESCRIPTION OF DIAGNOSTICS"),
613 "\n$msg{DESCRIPTION}\n";
614 }
615}
616
617sub unescape {
618 s {
619 E<
620 ( [A-Za-z]+ )
621 >
622 } {
623 do {
624 exists $HTML_Escapes{$1}
625 ? do { $HTML_Escapes{$1} }
626 : do {
f02a87df 627 warn "Unknown escape: E<$1> in $_";
4633a7c4
LW
628 "E<$1>";
629 }
630 }
631 }egx;
632}
633
634sub shorten {
635 my $line = $_[0];
774d564b 636 if (length($line) > 79 and index($line, "\n") == -1) {
4633a7c4
LW
637 my $space_place = rindex($line, ' ', 79);
638 if ($space_place != -1) {
639 substr($line, $space_place, 1) = "\n\t";
640 }
641 }
642 return $line;
643}
644
645
4633a7c4
LW
6461 unless $standalone; # or it'll complain about itself
647__END__ # wish diag dbase were more accessible