Icon for Dimension.orgIcon for TalentIcon for ToolsIcon for Doorways.org
Icon for ToolsIcon for Perl Tools

MMAgic Demo: MMAgic/Output.pm Source File

  • Main Page
  • Related Pages
  • Namespaces
  • Classes
  • Files
  • File List
  • File Members

MMAgic/Output.pm

Go to the documentation of this file.
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

Generated on Mon Dec 27 2010 15:15:42 for MMAgic Demo by  doxygen 1.7.1

www.dimension.org logo

(C)opyright 1998 - 2012 Dimension.org

WebMaster