00001 package MMAgic::Output;
00002 #
00003 # /MMAgic/Output.pm
00004 #
00005 # Generalized indented output stream.
00006 #
00007 # Provides formatting for output so generated files are readable.
00008 #
00009 # It is possible to set up with both an output handle of some type
00010 # and a string. The string will be used until flush() is called for
00011 # the first time, at which point the string will be sent to the handle
00012 # and further output will go to the handle directly.
00013 #
00014
00015 # use warnings;
00016
00017 use Fcntl qw(:flock);
00018 use File::Basename;
00019 use Symbol;
00020 use UNIVERSAL qw(can isa);
00021
00022 sub SEEK_SET { 0 }
00023
00024 ###########################################################################
00025 BEGIN
00026 {
00027 eval "require Win32::Pipe" if $windows = $^O eq 'MSWin32';
00028 }
00029
00030 ###########################################################################
00031 sub numberFor # $path, $digits
00032 #
00033 {
00034 my $path = shift;
00035 my $dgts = shift;
00036 my $nbr = 0;
00037 my $err = "file:\n $path\n in "
00038 . __PACKAGE__ . "::numberFor:\n ";
00039
00040 $path .= '.nbr';
00041
00042 if (-e $path)
00043 {
00044 # Number file already exists:
00045 open NBR, "+<$path"
00046 or die "Unable to open $err $!";
00047
00048 flock NBR, LOCK_EX
00049 or die "Unable to lock $err $!";
00050
00051 $nbr = <NBR>;
00052 $nbr = int($nbr);
00053 $nbr = 0 if length(++$nbr) > $dgts;
00054
00055 seek NBR, 0, SEEK_SET
00056 or die "Unable to reset $err $!";
00057 }
00058
00059 else
00060 {
00061 # Need to create new file:
00062 open NBR, ">$path"
00063 or die "Unable to create $err $!";
00064
00065 # (could be a race condition the first time through,
00066 # but once this exists it should be fairly stable)
00067
00068 flock NBR, LOCK_EX
00069 or die "Unable to lock $err $!";
00070 }
00071
00072 print NBR $nbr, "\n";
00073 flock NBR, LOCK_UN;
00074 close NBR;
00075
00076 $nbr
00077 }
00078
00079 ###########################################################################
00080 ###########################################################################
00081 sub new ($;%) # $class, ...
00082 #
00083 {
00084 my ($class, %parms) = @_;
00085 my $self =
00086 {
00087 indent => 0,
00088 indents => [],
00089 blankLn => 1,
00090 newLn => 1,
00091 };
00092
00093 # Loop until one of the parameters sets up a good handle
00094 # or until the code breaks out of the loop:
00095
00096 while (! $self->{handle})
00097 {
00098 if (defined $parms{string})
00099 {
00100 $self->{useStr} = 1;
00101 $self->{string} = $parms{string};
00102 delete $parms{string}; # only try once
00103 # but also allows us to keep looking for a handle
00104 # in subsequent loops
00105 }
00106
00107 elsif (my $pathname = $parms{pathname})
00108 {
00109 delete $parms{pathname}; # only try once
00110
00111 my ($name, $path, $suffix) = fileparse($pathname, qr/\.[^\.]*/);
00112
00113 if ($parms{backup})
00114 {
00115 # If file exists, rename to backup,
00116 # destroying old backup as necessary.
00117 # Specify suffix as parm, or use integer
00118 # to get default backup suffix ('.bak').
00119 my $backup = $parms{backup};
00120
00121 $backup = '.bak' if int($backup);
00122 $backup = ".$backup" unless $backup =~ /^\./;
00123 $suffix = '.log' unless $suffix;
00124 $pathname = "$path$name$suffix";
00125 $backup = "$path$name$backup";
00126
00127 unlink $backup if -e $backup;
00128 rename $pathname, $backup if -e $pathname;
00129 # (this will only work properly if used in
00130 # a single-threaded/single-process context)
00131 }
00132
00133 elsif ($parms{number})
00134 {
00135 # Create unique numbered suffixes for file:
00136 my $digits = int($parms{number});
00137 my $number = numberFor($pathname = "$path$name", $digits);
00138
00139 $pathname .= '.';
00140 $pathname .= '0' x ($digits - length($number));
00141 $pathname .= $number;
00142 }
00143
00144 else
00145 {
00146 $pathname = "$path$name$suffix"
00147 }
00148
00149 # Twisted, read carefully:
00150 if (-e $pathname && ! -w $pathname)
00151 {
00152 print STDERR
00153 ("Output path $path exists and is not writable");
00154 }
00155
00156 elsif (! ($self->{handle} = gensym))
00157 {
00158 print STDERR
00159 ("Unable to use gensym() to create file handle\n ", $!);
00160 }
00161
00162 elsif (! open($self->{handle}, ">$pathname"))
00163 {
00164 # failed, cleanup:
00165 print STDERR
00166 ("Unable to write to file\n ", $pathname, "\n ", $!);
00167
00168 delete $self->{handle};
00169 }
00170
00171 else
00172 {
00173 $self->{pathname} = $pathname;
00174 last; # done
00175 }
00176 }
00177
00178 elsif (my $pipe = $parms{nmpipe})
00179 {
00180 my $name = $parms{nmpipe};
00181
00182 delete $parms{nmpipe}; # only try once
00183
00184 next unless $windows; # don't have UNIX code yet
00185 last # done
00186 # Not working now???(04/16/01)???
00187 # How to make this also work on UNIX???
00188 if $self->{nmpipe} =
00189 new Win32::Pipe("\\\\.\\pipe\\$name");
00190 }
00191
00192 elsif ($parms{handle})
00193 {
00194 $self->{handle} = $parms{handle};
00195 delete $parms{handle} # only try once
00196 }
00197
00198 elsif ($self->{useStr})
00199 {
00200 # We apparently just have a string defined,
00201 # so bomb out because we'll never have a handle:
00202 last
00203 }
00204
00205 else
00206 {
00207 # Nothing else has worked, so just do something and quit:
00208 $self->{handle} = $parms{output} || \*STDOUT;
00209
00210 if (defined($parms{flush}) && $parms{flush} eq 'auto')
00211 {
00212 # Set auto-flush:
00213 my $old = select($self->{handle});
00214
00215 $| = 1;
00216 select($old);
00217 }
00218 }
00219 };
00220
00221 return bless $self, $class;
00222 }
00223
00224 ###########################################################################
00225 ###########################################################################
00226 sub bypass # $self
00227 # Return a new output stream that bypasses any string buffering.
00228 # If no string buffering, return this object.
00229 #
00230 {
00231 my $self = shift;
00232
00233 return $self
00234 unless $self->{useStr};
00235
00236 return new MMAgic::Output(handle => $self->{handle})
00237 if $self->{handle};
00238
00239 # Don't have code for pipes yet:
00240 $self
00241 }
00242
00243 ###########################################################################
00244 sub clear # $self
00245 # Clear any pending buffered output data.
00246 #
00247 {
00248 my $self = shift;
00249
00250 delete $self->{string}
00251 if defined $self->{string}
00252 && length $self->{string};
00253 }
00254
00255 ###########################################################################
00256 sub close # $self
00257 # Flush pending output and close stream.
00258 # Only close handle if it was opened from a pathname,
00259 # otherwise the handle was passed in already open
00260 # and it is someone else's responsibility to close it.
00261 #
00262 {
00263 my $self = shift;
00264
00265 $self->flush;
00266
00267 if ($self->{pathname})
00268 {
00269 close $self->{handle};
00270 delete $self->{handle};
00271 delete $self->{pathname};
00272 }
00273
00274 delete $self->{handle} if defined $self->{handle};
00275 delete $self->{nmpipe} if defined $self->{nmpipe};
00276 delete $self->{string} if defined $self->{string};
00277 }
00278
00279 ###########################################################################
00280 sub default # $self
00281 # Return true if default output was used.
00282 #
00283 {
00284 $_[0]->{handle} == \*STDOUT ? 'STDOUT' :
00285 $_[0]->{handle} == \*STDERR ? 'STDERR' : undef
00286 }
00287
00288 ###########################################################################
00289 sub flush # $self
00290 # Flush pending output.
00291 # Perl doesn't directly support any flushing functionality,
00292 # it makes do with autoflush, but we have string buffering to flush:
00293 #
00294 {
00295 my $self = shift;
00296
00297 if ($self->{useStr})
00298 {
00299 # After this data will go directly to the output handle or pipe:
00300 delete $self->{useStr};
00301
00302 # Process any existing string data:
00303 $self->process($self->{string})
00304 if defined $self->{string}
00305 && length $self->{string};
00306
00307 # OK, we can jettison this now:
00308 delete $self->{string};
00309 }
00310 }
00311
00312 ###########################################################################
00313 sub process # $self, @list
00314 # Print or store the specified items.
00315 #
00316 {
00317 my $self = shift;
00318
00319 # lock($self);
00320
00321 # Look for string first to handle buffering cases:
00322
00323 if ($self->{useStr})
00324 {
00325 $self->{string} .= join '', @_;
00326 }
00327
00328 elsif (ref($self->{nmpipe}) eq 'Win32::Pipe')
00329 {
00330 $self->{nmpipe}->Write(@_);
00331 }
00332
00333 elsif ($self->{handle})
00334 {
00335 CORE::print ${$self->{handle}} (@_);
00336 }
00337
00338 return $self;
00339 }
00340
00341 ###########################################################################
00342 sub string # $self, $clear
00343 # Return the string storage for internal buffer streams.
00344 #
00345 {
00346 my $self = shift;
00347 my $str = $self->{string};
00348
00349 $self->{string} = ''
00350 if shift() && $self->{string};
00351
00352 $str
00353 }
00354
00355 ###########################################################################
00356 ###########################################################################
00357 sub indent # $self
00358 # Generate beginning-of-line sequence.
00359 # Can assume beginning of line, use to add markers.
00360 #
00361 {
00362 my $self = shift;
00363
00364 return $self unless $self->{indent};
00365
00366 $self->process(' ' x $self->{indent});
00367 }
00368
00369 ###########################################################################
00370 sub clearIndent ($)
00371 # Push "no" indent onto stack:
00372 #
00373 {
00374 my $self = shift;
00375
00376 push @{$self{indents}}, $self->{indent};
00377 $self->{indent} = 0;
00378
00379 return $self;
00380 }
00381
00382 ###########################################################################
00383 sub pushIndent ($;$)
00384 # Push indentation level.
00385 #
00386 {
00387 my $self = shift;
00388 my $incr = shift || 2;
00389
00390 # lock($self);
00391
00392 push @{$self{indents}}, $self->{indent};
00393 $self->{indent} += $incr;
00394
00395 return $self;
00396 }
00397
00398 ###########################################################################
00399 sub pushLine ($$;$) # $self, $open, $incr
00400 # Push indentation level, print open sequence, then newline.
00401 #
00402 {
00403 my $self = shift;
00404 my $open = shift;
00405 my $incr = shift || 2;
00406
00407 $self->pushIndent($incr);
00408 $self->print($open) if $open;
00409
00410 return $self->newLine(1); # don't want blank lines after pushLine
00411 }
00412
00413 ###########################################################################
00414 sub popIndent ($) # $self
00415 # Pop indentation level.
00416 #
00417 {
00418 my $self = shift;
00419
00420 # lock($self);
00421
00422 $self->{indent} = pop @{$self{indents}};
00423
00424 return $self;
00425 }
00426
00427 ###########################################################################
00428 sub popLine ($$$) # $self, $close, $comma
00429 # Print close sequence, pop indentation level, then newline.
00430 #
00431 {
00432 my ($self, $close, $comma) = @_;
00433
00434 $self->print($close) if $close;
00435 $self->print($comma) if $comma;
00436
00437 return $self->newLine()->popIndent();
00438 }
00439
00440 ###########################################################################
00441 sub starter # $self
00442 # Provide tag at beginning of line.
00443 #
00444 {
00445 # This version does nothing.
00446 return shift;
00447 }
00448
00449 ###########################################################################
00450 ###########################################################################
00451 sub blankLine ($) # $self
00452 # Ensure that the next output will be after a blank line.
00453 #
00454 {
00455 my $self = shift;
00456
00457 # lock($self);
00458
00459 $self->newLine;
00460
00461 unless ($self->{"blankLn"})
00462 {
00463 $self->process("\n");
00464 $self->{blankLn} = 1;
00465 }
00466
00467 return $self;
00468 }
00469
00470 ###########################################################################
00471 sub newLine ($$) # $self, $noBlank
00472 # Ensure that the next output will be on a new line.
00473 #
00474 {
00475 my $self = shift;
00476
00477 # lock($self);
00478
00479 unless ($self->{newLn})
00480 {
00481 $self->process("\n");
00482 $self->{newLn} = 1;
00483 $self->{blankLn} = 1 if shift;
00484 }
00485
00486 return $self;
00487 }
00488
00489 ###########################################################################
00490 sub print ($@) # $self, ...
00491 # Print to file handle associated with output object.
00492 #
00493 {
00494 my $self = shift;
00495
00496 # lock($self);
00497
00498 map {
00499 my $arg = $_; # using foreach would alias $arg to actual arguments,
00500 # making it impossible to reassign below
00501
00502 # Individual arguments (as opposed to interpolations) are checked and
00503 # handled specially if appropriate, which is really good for using
00504 # this class for tracing and so forth:
00505
00506 if (! defined($arg))
00507 {
00508 # print STDERR "{UNDEF}\n";
00509 $arg = '{undef}';
00510 }
00511
00512 elsif (isa($arg, 'Win32::OLE'))
00513 {
00514 $arg = sprintf "{OLE %s %s}", Win32::OLE->QueryObjectType($arg);
00515 }
00516
00517 elsif (can($arg, 'image'))
00518 {
00519 $arg = $arg->image;
00520 }
00521
00522 # (note that things embedded within double-quotes will still
00523 # have been interpreted in a more normal manner)
00524
00525 for my $item (split /(\n)/, $arg)
00526 {
00527 if ($item eq "\n")
00528 {
00529 $self->{blankLn} = 1 if $self->{newLn};
00530 $self->{newLn } = 1;
00531 }
00532
00533 else
00534 {
00535 $self->starter->indent
00536 if $self->{newLn};
00537
00538 $self->{blankLn} = 0;
00539 $self->{newLn } = 0;
00540 }
00541
00542 $self->process($item);
00543 }
00544 } @_;
00545
00546 $self
00547 }
00548
00549 ###########################################################################
00550 sub printf ($$;@) # $self, $format, ...
00551 # Print to file handle associated with TriState object.
00552 #
00553 {
00554 my $self = shift;
00555 my $form = shift;
00556
00557 $self->process(sprintf($form, @_));
00558
00559 return $self;
00560 }
00561
00562 ###########################################################################
00563 sub display # $self, $item, %param
00564 {
00565 my ($self, $item, %param) = @_;
00566 my $depth = $param{depth} || 0;
00567 my $maxDep = $param{maxDepth} || 3;
00568 my $prefix = $param{prefix} || '';
00569
00570 $item = ${$item}
00571 if isa($item, 'SCALAR')
00572 && ! isa($item, 'Regexp');
00573
00574 if (! defined $item)
00575 {
00576 $self->print($prefix, '<undef>')->newLine;
00577 }
00578
00579 elsif (isa($item, 'ARRAY'))
00580 {
00581 $prefix = '###'
00582 unless $prefix;
00583
00584 $prefix .= ' ' . ref $item
00585 unless 'ARRAY' eq ref $item;
00586
00587 $self->newLine()->print($prefix)->newLine;
00588 $self->pushLine('[');
00589
00590 for my $item (@$item)
00591 {
00592 if ($depth < $maxDep)
00593 {
00594 $self->display
00595 ($item,
00596 depth => $depth + 1,
00597 maxDepth => $maxDep);
00598 }
00599
00600 else
00601 {
00602 $self->print($item, ',')->newLine;
00603 }
00604 }
00605
00606 $self->popLine('],');
00607 }
00608
00609 elsif (isa($item, 'HASH'))
00610 {
00611 $prefix = '###'
00612 unless $prefix;
00613
00614 $prefix .= ' ' . ref $item
00615 unless 'HASH' eq ref $item;
00616
00617 $self->newLine();
00618
00619 if (ref($item) eq 'HASH')
00620 {
00621 $self->print($prefix);
00622 }
00623
00624 else
00625 {
00626 $self->print($prefix) if $prefix;
00627 $self->print('# ', ref($item));
00628 }
00629
00630 $self->newLine()->pushLine('{');
00631
00632 for my $key (keys %$item)
00633 {
00634 my $keyPrefix = $key =~ /^[^A-Za-z]/ ||
00635 $key =~ /[^A-Za-z0-9_\-]/
00636 ? "'$key' => "
00637 : "$key => ";
00638
00639 if ($depth < $maxDep)
00640 {
00641 $self->display
00642 ($item->{$key},
00643 prefix => $keyPrefix,
00644 depth => $depth + 1,
00645 maxDepth => $maxDep);
00646 }
00647
00648 else
00649 {
00650 $self->print($keyPrefix, $item, ',')->newLine;
00651 }
00652 }
00653
00654 $self->popLine('},');
00655 }
00656
00657 elsif (isa($item, 'Regexp'))
00658 {
00659 # Found a regular expression:
00660 my $pattern = undef;
00661
00662 $pattern = $1 if $item =~ m|\(\?-xism:(.*)\)$|;
00663 $pattern = "$item" unless $pattern;
00664
00665 $self->print($prefix, '/', $pattern, '/,')->newLine;
00666 }
00667
00668 elsif (ref($item) || $item =~ /^\d*.?\d+$/)
00669 {
00670 # Found a number or an object:
00671 $self->print($prefix, $item, ',')->newLine;
00672 }
00673
00674 elsif ($item =~ /\n./)
00675 {
00676 # Split long text into multiple lines:
00677 $self->print($prefix, '<<TEXT,')
00678 ->clearIndent->newLine;
00679
00680 map {
00681 $self->print($_)->newLine;
00682 } split(/\n/, $item);
00683
00684 $self->print('TEXT')
00685 ->popIndent->newLine;
00686 }
00687
00688 else
00689 {
00690 # Treat it as a string:
00691 $self->print($prefix, "'", $item, "',")->newLine;
00692 }
00693 }
00694
00695 ###########################################################################
00696 ###########################################################################
00697 my $deprecated;
00698
00699 sub dumpref # $self, $target, %param
00700 #
00701 {
00702 my ($self, $target, %param) = @_;
00703 my $depth = $param{depth} || 0;
00704 my $maxDep = $param{maxDepth} || 3;
00705 my $prefix = $param{prefix} || '';
00706
00707 $self->warning(__PACKAGE__, '::dumpref() deprecated in favor of ',
00708 __PACKAGE__, '::display()')
00709 unless $deprecated++;
00710
00711 $target = ${$target}
00712 if isa($target, 'SCALAR')
00713 && ! isa($target, 'Regexp');
00714
00715 if (isa($target, 'ARRAY'))
00716 {
00717 $self->newLine()->print($prefix ? $prefix : '###')->newLine;
00718 $self->pushLine('[');
00719
00720 for my $item (@$target)
00721 {
00722 if ($depth < $maxDep)
00723 {
00724 $self->dumpref
00725 ($item,
00726 depth => $depth + 1,
00727 maxDepth => $maxDep);
00728 }
00729
00730 else
00731 {
00732 $self->print($item, ',')->newLine;
00733 }
00734 }
00735
00736 $self->popLine('],');
00737 }
00738
00739 elsif (isa($target, 'HASH'))
00740 {
00741 $self->newLine();
00742
00743 if (ref($target) eq 'HASH')
00744 {
00745 $self->print($prefix ? $prefix : '###');
00746 }
00747
00748 else
00749 {
00750 $self->print($prefix) if $prefix;
00751 $self->print('# ', ref($target));
00752 }
00753
00754 $self->newLine()->pushLine('{');
00755
00756 for my $key (keys %$target)
00757 {
00758 my $keyPrefix = $key =~ /^[^A-Za-z]/ ||
00759 $key =~ /[^A-Za-z0-9_\-]/
00760 ? "'$key' => "
00761 : "$key => ";
00762
00763 if ($depth < $maxDep)
00764 {
00765 $self->dumpref
00766 ($target->{$key},
00767 prefix => $keyPrefix,
00768 depth => $depth + 1,
00769 maxDepth => $maxDep);
00770 }
00771
00772 else
00773 {
00774 $self->print($keyPrefix, $target, ',')->newLine;
00775 }
00776 }
00777
00778 $self->popLine('},');
00779 }
00780
00781 elsif (isa($target, 'Regexp'))
00782 {
00783 # Found a regular expression:
00784 my $pattern = undef;
00785
00786 $pattern = $1 if $target =~ m|\(\?-xism:(.*)\)$|;
00787 $pattern = "$target" unless $pattern;
00788
00789 $self->print($prefix, '/', $pattern, '/,')->newLine;
00790 }
00791
00792 elsif (ref($target) || $target =~ /^\d*.?\d+$/)
00793 {
00794 # Found a number or an object:
00795 $self->print($prefix, $target, ',')->newLine;
00796 }
00797
00798 elsif ($target =~ /\n./)
00799 {
00800 # Split long text into multiple lines:
00801 $self->print($prefix, '<<TEXT,')
00802 ->clearIndent->newLine;
00803
00804 map {
00805 $self->print($_)->newLine;
00806 } split(/\n/, $target);
00807
00808 $self->print('TEXT')
00809 ->popIndent->newLine;
00810
00811 # my @lines = split(/\n/, $target);
00812 #
00813 # $self->dumpref
00814 # (\@lines,
00815 # prefix => $prefix,
00816 # depth => $depth + 1,
00817 # maxDepth => $maxDep);
00818 }
00819
00820 elsif (defined $target)
00821 {
00822 # Treat it as a string:
00823 $self->print($prefix, "'", $target, "',")->newLine;
00824 }
00825 }
00826
00827 ###########################################################################
00828 ###########################################################################
00829 sub TIEHANDLE # $classname, @list
00830 #
00831 {
00832 my $clss = shift;
00833
00834 $clss->new(@_)
00835 }
00836
00837 ###########################################################################
00838 sub WRITE # $self, @list
00839 #
00840 {
00841 print STDERR "WRITE not implemented by ", __PACKAGE__, "\n";
00842 }
00843
00844 ###########################################################################
00845 sub PRINT # $self, @list
00846 #
00847 {
00848 my $self = shift;
00849
00850 $self->print(@_)
00851 }
00852
00853 ###########################################################################
00854 sub PRINTF # $self, $fmt, @list
00855 #
00856 {
00857 my $self = shift;
00858
00859 $self->printf(@_)
00860 }
00861
00862 ###########################################################################
00863 sub CLOSE # $self
00864 #
00865 {
00866 $_[0]->close;
00867 }
00868
00869 ###########################################################################
00870 sub DESTROY # $self
00871 #
00872 {
00873 $_[0]->close;
00874 }
00875
00876 ###########################################################################
00877 sub UNTIE # $self
00878 #
00879 {
00880 $_[0]->close;
00881 }
00882
00883 1