Upgrade to Test::Simple 0.64_03
[p5sagit/p5-mst-13.2.git] / lib / Pod / Parser.pm
index b81b080..d242a51 100644 (file)
@@ -1,10 +1,7 @@
 #############################################################################
 # Pod/Parser.pm -- package which defines a base class for parsing POD docs.
 #
-# Based on Tom Christiansen's Pod::Text module
-# (with extensive modifications).
-#
-# Copyright (C) 1996-1999 Tom Christiansen. All rights reserved.
+# Copyright (C) 1996-2000 by Bradford Appleton. All rights reserved.
 # This file is part of "PodParser". PodParser is free software;
 # you can redistribute it and/or modify it under the same terms
 # as Perl itself.
@@ -13,8 +10,8 @@
 package Pod::Parser;
 
 use vars qw($VERSION);
-$VERSION = 1.08;   ## Current version of this package
-require  5.004;    ## requires this Perl version or later
+$VERSION = 1.35;  ## Current version of this package
+require  5.005;    ## requires this Perl version or later
 
 #############################################################################
 
@@ -58,9 +55,9 @@ Pod::Parser - base class for creating POD filters and translators
     sub interior_sequence { 
         my ($parser, $seq_command, $seq_argument) = @_;
         ## Expand an interior sequence; sample actions might be:
-        return "*$seq_argument*"     if ($seq_command = 'B');
-        return "`$seq_argument'"     if ($seq_command = 'C');
-        return "_${seq_argument}_'"  if ($seq_command = 'I');
+        return "*$seq_argument*"     if ($seq_command eq 'B');
+        return "`$seq_argument'"     if ($seq_command eq 'C');
+        return "_${seq_argument}_'"  if ($seq_command eq 'I');
         ## ... other sequence commands and their resulting text
     }
 
@@ -74,7 +71,7 @@ Pod::Parser - base class for creating POD filters and translators
 
 =head1 REQUIRES
 
-perl5.004, Pod::InputObjects, Exporter, FileHandle, Carp
+perl5.005, Pod::InputObjects, Exporter, Symbol, Carp
 
 =head1 EXPORTS
 
@@ -143,7 +140,60 @@ to avoid name collisions.
 
 For the most part, the B<Pod::Parser> base class should be able to
 do most of the input parsing for you and leave you free to worry about
-how to intepret the commands and translate the result.
+how to interpret the commands and translate the result.
+
+Note that all we have described here in this quick overview is the
+simplest most straightforward use of B<Pod::Parser> to do stream-based
+parsing. It is also possible to use the B<Pod::Parser::parse_text> function
+to do more sophisticated tree-based parsing. See L<"TREE-BASED PARSING">.
+
+=head1 PARSING OPTIONS
+
+A I<parse-option> is simply a named option of B<Pod::Parser> with a
+value that corresponds to a certain specified behavior. These various
+behaviors of B<Pod::Parser> may be enabled/disabled by setting
+or unsetting one or more I<parse-options> using the B<parseopts()> method.
+The set of currently accepted parse-options is as follows:
+
+=over 3
+
+=item B<-want_nonPODs> (default: unset)
+
+Normally (by default) B<Pod::Parser> will only provide access to
+the POD sections of the input. Input paragraphs that are not part
+of the POD-format documentation are not made available to the caller
+(not even using B<preprocess_paragraph()>). Setting this option to a
+non-empty, non-zero value will allow B<preprocess_paragraph()> to see
+non-POD sections of the input as well as POD sections. The B<cutting()>
+method can be used to determine if the corresponding paragraph is a POD
+paragraph, or some other input paragraph.
+
+=item B<-process_cut_cmd> (default: unset)
+
+Normally (by default) B<Pod::Parser> handles the C<=cut> POD directive
+by itself and does not pass it on to the caller for processing. Setting
+this option to a non-empty, non-zero value will cause B<Pod::Parser> to
+pass the C<=cut> directive to the caller just like any other POD command
+(and hence it may be processed by the B<command()> method).
+
+B<Pod::Parser> will still interpret the C<=cut> directive to mean that
+"cutting mode" has been (re)entered, but the caller will get a chance
+to capture the actual C<=cut> paragraph itself for whatever purpose
+it desires.
+
+=item B<-warnings> (default: unset)
+
+Normally (by default) B<Pod::Parser> recognizes a bare minimum of
+pod syntax errors and warnings and issues diagnostic messages
+for errors, but not for warnings. (Use B<Pod::Checker> to do more
+thorough checking of POD syntax.) Setting this option to a non-empty,
+non-zero value will cause B<Pod::Parser> to issue diagnostics for
+the few warnings it recognizes as well as the errors.
+
+=back
+
+Please see L<"parseopts()"> for a complete description of the interface
+for the setting and unsetting of parse-options.
 
 =cut
 
@@ -154,12 +204,17 @@ use strict;
 #use diagnostics;
 use Pod::InputObjects;
 use Carp;
-use FileHandle;
 use Exporter;
+BEGIN {
+   if ($] < 5.6) {
+      require Symbol;
+      import Symbol;
+   }
+}
 @ISA = qw(Exporter);
 
 ## These "variables" are used as local "glob aliases" for performance
-use vars qw(%myData @input_stack);
+use vars qw(%myData %myOpts @input_stack);
 
 #############################################################################
 
@@ -547,18 +602,20 @@ The value returned should correspond to the new text to use in its
 place If the empty string is returned or an undefined value is
 returned, then the given C<$text> is ignored (not processed).
 
-This method is invoked after gathering up all thelines in a paragraph
+This method is invoked after gathering up all the lines in a paragraph
+and after determining the cutting state of the paragraph,
 but before trying to further parse or interpret them. After
 B<preprocess_paragraph()> returns, the current cutting state (which
 is returned by C<$self-E<gt>cutting()>) is examined. If it evaluates
-to false then input text (including the given C<$text>) is cut (not
+to true then input text (including the given C<$text>) is cut (not
 processed) until the next POD directive is encountered.
 
 Please note that the B<preprocess_line()> method is invoked I<before>
 the B<preprocess_paragraph()> method. After all (possibly preprocessed)
-lines in a paragraph have been assembled together and it has been
+lines in a paragraph have been assembled together and either it has been
 determined that the paragraph is part of the POD documentation from one
-of the selected sections, then B<preprocess_paragraph()> is invoked.
+of the selected sections or the C<-want_nonPODs> option is true,
+then B<preprocess_paragraph()> is invoked.
 
 The base class implementation of this method returns the given text.
 
@@ -574,8 +631,9 @@ sub preprocess_paragraph {
 =head1 METHODS FOR PARSING AND PROCESSING
 
 B<Pod::Parser> provides several methods to process input text. These
-methods typically won't need to be overridden, but subclasses may want
-to invoke them to exploit their functionality.
+methods typically won't need to be overridden (and in some cases they
+can't be overridden), but subclasses may want to invoke them to exploit
+their functionality.
 
 =cut
 
@@ -589,11 +647,11 @@ to invoke them to exploit their functionality.
 
 This method is useful if you need to perform your own interpolation 
 of interior sequences and can't rely upon B<interpolate> to expand
-them in simple bottom-up order order.
+them in simple bottom-up order.
 
 The parameter C<$text> is a string or block of text to be parsed
 for interior sequences; and the parameter C<$line_num> is the
-line number curresponding to the beginning of C<$text>.
+line number corresponding to the beginning of C<$text>.
 
 B<parse_text()> will parse the given text into a parse-tree of "nodes."
 and interior-sequences.  Each "node" in the parse tree is either a
@@ -629,6 +687,31 @@ is a reference to the interior-sequence object.
 [I<NOTE>: If the B<interior_sequence()> method is specified, then it is
 invoked according to the interface specified in L<"interior_sequence()">].
 
+=item B<-expand_text> =E<gt> I<code-ref>|I<method-name>
+
+Normally, the parse-tree returned by B<parse_text()> will contain a
+text-string for each contiguous sequence of characters outside of an
+interior-sequence. Specifying B<-expand_text> tells B<parse_text()> to
+"preprocess" every such text-string it sees by invoking the referenced
+function (or named method of the parser object) and using the return value
+as the preprocessed (or "expanded") result. [Note that if the result is
+an interior-sequence, then it will I<not> be expanded as specified by the
+B<-expand_seq> option; Any such recursive expansion needs to be handled by
+the specified callback routine.]
+
+If a subroutine reference was given, it is invoked as:
+
+  &$code_ref( $parser, $text, $ptree_node )
+
+and if a method-name was given, it is invoked as:
+
+  $parser->method_name( $text, $ptree_node )
+
+where C<$parser> is a reference to the parser object, C<$text> is the
+text-string encountered, and C<$ptree_node> is a reference to the current
+node in the parse-tree (usually an interior-sequence object or else the
+top-level node of the parse-tree).
+
 =item B<-expand_ptree> =E<gt> I<code-ref>|I<method-name>
 
 Rather than returning a C<Pod::ParseTree>, pass the parse-tree as an
@@ -650,11 +733,6 @@ is a reference to the parse-tree object.
 
 =cut
 
-## This global regex is used to see if the text before a '>' inside
-## an interior sequence looks like '-' or '=', but not '--' or '=='
-use vars qw( $ARROW_RE );
-$ARROW_RE = join('', qw{ (?: [^=]+= | [^-]+- )$ });  
-
 sub parse_text {
     my $self = shift;
     local $_ = '';
@@ -662,17 +740,19 @@ sub parse_text {
     ## Get options and set any defaults
     my %opts = (ref $_[0]) ? %{ shift() } : ();
     my $expand_seq   = $opts{'-expand_seq'}   || undef;
+    my $expand_text  = $opts{'-expand_text'}  || undef;
     my $expand_ptree = $opts{'-expand_ptree'} || undef;
 
     my $text = shift;
     my $line = shift;
     my $file = $self->input_file();
-    my ($cmd, $prev)  = ('', '');
+    my $cmd  = "";
 
     ## Convert method calls into closures, for our convenience
     my $xseq_sub   = $expand_seq;
+    my $xtext_sub  = $expand_text;
     my $xptree_sub = $expand_ptree;
-    if ($expand_seq eq 'interior_sequence') {
+    if (defined $expand_seq  and  $expand_seq eq 'interior_sequence') {
         ## If 'interior_sequence' is the method to use, we have to pass
         ## more than just the sequence object, we also need to pass the
         ## sequence name and text.
@@ -683,8 +763,9 @@ sub parse_text {
         };
     }
     ref $xseq_sub    or  $xseq_sub   = sub { shift()->$expand_seq(@_) };
+    ref $xtext_sub   or  $xtext_sub  = sub { shift()->$expand_text(@_) };
     ref $xptree_sub  or  $xptree_sub = sub { shift()->$expand_ptree(@_) };
-    
+
     ## Keep track of the "current" interior sequence, and maintain a stack
     ## of "in progress" sequences.
     ##
@@ -696,50 +777,91 @@ sub parse_text {
     ##
     my $seq       = Pod::ParseTree->new();
     my @seq_stack = ($seq);
+    my ($ldelim, $rdelim) = ('', '');
 
-    ## Iterate over all sequence starts/stops, newlines, & text
-    ## (NOTE: split with capturing parens keeps the delimiters)
+    ## Iterate over all sequence starts text (NOTE: split with
+    ## capturing parens keeps the delimiters)
     $_ = $text;
-    for ( split /([A-Z]<|>|\n)/ ) {
-        ## Keep track of line count
-        ++$line  if ($_ eq "\n");
+    my @tokens = split /([A-Z]<(?:<+\s)?)/;
+    while ( @tokens ) {
+        $_ = shift @tokens;
         ## Look for the beginning of a sequence
-        if ( /^([A-Z])(<)$/ ) {
-            ## Push a new sequence onto the stack on of those "in-progress"
+        if ( /^([A-Z])(<(?:<+\s)?)$/ ) {
+            ## Push a new sequence onto the stack of those "in-progress"
+            my $ldelim_orig;
+            ($cmd, $ldelim_orig) = ($1, $2);
+            ($ldelim = $ldelim_orig) =~ s/\s+$//;
+            ($rdelim = $ldelim) =~ tr/</>/;
             $seq = Pod::InteriorSequence->new(
-                       -name   => ($cmd = $1),
-                       -ldelim => $2,     -rdelim => '',
-                       -file   => $file,  -line   => $line
+                       -name   => $cmd,
+                       -ldelim => $ldelim_orig,  -rdelim => $rdelim,
+                       -file   => $file,    -line   => $line
                    );
             (@seq_stack > 1)  and  $seq->nested($seq_stack[-1]);
             push @seq_stack, $seq;
         }
-        ## Look for sequence ending (preclude '->' and '=>' inside C<...>)
-        elsif ( (@seq_stack > 1)  and
-                /^>$/ and ($cmd ne 'C' or $prev !~ /$ARROW_RE/o) )
-        {
-            ## End of current sequence, record terminating delimiter
-            $seq->rdelim($_);
-            ## Pop it off the stack of "in progress" sequences
-            pop @seq_stack;
-            ## Append result to its parent in current parse tree
-            $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) : $seq);
-            ## Remember the current cmd-name
-            $cmd = (@seq_stack > 1) ? $seq_stack[-1]->name : '';
+        ## Look for sequence ending
+        elsif ( @seq_stack > 1 ) {
+            ## Make sure we match the right kind of closing delimiter
+            my ($seq_end, $post_seq) = ("", "");
+            if ( ($ldelim eq '<'   and  /\A(.*?)(>)/s)
+                 or  /\A(.*?)(\s+$rdelim)/s )
+            {
+                ## Found end-of-sequence, capture the interior and the
+                ## closing the delimiter, and put the rest back on the
+                ## token-list
+                $post_seq = substr($_, length($1) + length($2));
+                ($_, $seq_end) = ($1, $2);
+                (length $post_seq)  and  unshift @tokens, $post_seq;
+            }
+            if (length) {
+                ## In the middle of a sequence, append this text to it, and
+                ## dont forget to "expand" it if that's what the caller wanted
+                $seq->append($expand_text ? &$xtext_sub($self,$_,$seq) : $_);
+                $_ .= $seq_end;
+            }
+            if (length $seq_end) {
+                ## End of current sequence, record terminating delimiter
+                $seq->rdelim($seq_end);
+                ## Pop it off the stack of "in progress" sequences
+                pop @seq_stack;
+                ## Append result to its parent in current parse tree
+                $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq)
+                                                   : $seq);
+                ## Remember the current cmd-name and left-delimiter
+                if(@seq_stack > 1) {
+                    $cmd = $seq_stack[-1]->name;
+                    $ldelim = $seq_stack[-1]->ldelim;
+                    $rdelim = $seq_stack[-1]->rdelim;
+                } else {
+                    $cmd = $ldelim = $rdelim = '';
+                }
+            }
         }
-        else {
-            ## In the middle of a sequence, append this text to it
-            $seq->append($_)  if $_;
+        elsif (length) {
+            ## In the middle of a sequence, append this text to it, and
+            ## dont forget to "expand" it if that's what the caller wanted
+            $seq->append($expand_text ? &$xtext_sub($self,$_,$seq) : $_);
         }
-        ## Remember the "current" sequence and the previously seen token
-        ($seq, $prev) = ( $seq_stack[-1], $_ );
+        ## Keep track of line count
+        $line += s/\r*\n//;
+        ## Remember the "current" sequence
+        $seq = $seq_stack[-1];
     }
 
     ## Handle unterminated sequences
+    my $errorsub = (@seq_stack > 1) ? $self->errorsub() : undef;
     while (@seq_stack > 1) {
        ($cmd, $file, $line) = ($seq->name, $seq->file_line);
+       $ldelim  = $seq->ldelim;
+       ($rdelim = $ldelim) =~ tr/</>/;
+       $rdelim  =~ s/^(\S+)(\s*)$/$2$1/;
        pop @seq_stack;
-       warn "** Unterminated $cmd<...> at $file line $line\n";
+       my $errmsg = "*** ERROR: unterminated ${cmd}${ldelim}...${rdelim}".
+                    " at line $line in file $file\n";
+       (ref $errorsub) and &{$errorsub}($errmsg)
+           or (defined $errorsub) and $self->$errorsub($errmsg)
+               or  warn($errmsg);
        $seq_stack[-1]->append($expand_seq ? &$xseq_sub($self,$seq) : $seq);
        $seq = $seq_stack[-1];
     }
@@ -786,7 +908,8 @@ This method takes the text of a POD paragraph to be processed, along
 with its corresponding line number, and invokes the appropriate method
 (one of B<command()>, B<verbatim()>, or B<textblock()>).
 
-This method does I<not> usually need to be overridden by subclasses.
+For performance reasons, this method is invoked directly without any
+dynamic lookup; Hence subclasses may I<not> override it!
 
 =end __PRIVATE__
 
@@ -794,15 +917,21 @@ This method does I<not> usually need to be overridden by subclasses.
 
 sub parse_paragraph {
     my ($self, $text, $line_num) = @_;
-    local *myData = $self;  ## an alias to avoid deref-ing overhead
+    local *myData = $self;  ## alias to avoid deref-ing overhead
+    local *myOpts = ($myData{_PARSEOPTS} ||= {});  ## get parse-options
     local $_;
 
-    ## This is the end of a non-empty paragraph
+    ## See if we want to preprocess nonPOD paragraphs as well as POD ones.
+    my $wantNonPods = $myOpts{'-want_nonPODs'};
+
+    ## Update cutting status
+    $myData{_CUTTING} = 0 if $text =~ /^={1,2}\S/;
+
+    ## Perform any desired preprocessing if we wanted it this early
+    $wantNonPods  and  $text = $self->preprocess_paragraph($text, $line_num);
+
     ## Ignore up until next POD directive if we are cutting
-    if ($myData{_CUTTING}) {
-       return  unless ($text =~ /^={1,2}\S/);
-       $myData{_CUTTING} = 0;
-    }
+    return if $myData{_CUTTING};
 
     ## Now we know this is block of text in a POD section!
 
@@ -820,10 +949,13 @@ sub parse_paragraph {
         $self->is_selected($text)  or  return ($myData{_CUTTING} = 1);
     }
 
-    ## Perform any desired preprocessing and re-check the "cutting" state
-    $text = $self->preprocess_paragraph($text, $line_num);
-    return 1  unless ((defined $text) and (length $text));
-    return 1  if ($myData{_CUTTING});
+    ## If we havent already, perform any desired preprocessing and
+    ## then re-check the "cutting" state
+    unless ($wantNonPods) {
+       $text = $self->preprocess_paragraph($text, $line_num);
+       return 1  unless ((defined $text) and (length $text));
+       return 1  if ($myData{_CUTTING});
+    }
 
     ## Look for one of the three types of paragraphs
     my ($pfx, $cmd, $arg, $sep) = ('', '', '', '');
@@ -834,13 +966,12 @@ sub parse_paragraph {
         ## and whatever sequence of characters was used to separate them
         $pfx = $1;
         $_ = substr($text, length $pfx);
-        $sep = /(\s+)(?=\S)/ ? $1 : '';
-        ($cmd, $text) = split(" ", $_, 2);
+        ($cmd, $sep, $text) = split /(\s+)/, $_, 2; 
         ## If this is a "cut" directive then we dont need to do anything
         ## except return to "cutting" mode.
         if ($cmd eq 'cut') {
            $myData{_CUTTING} = 1;
-           return;
+           return  unless $myOpts{'-process_cut_cmd'};
         }
     }
     ## Save the attributes indicating how the command was specified.
@@ -913,6 +1044,9 @@ sub parse_from_filehandle {
     my $self = shift;
     my %opts = (ref $_[0] eq 'HASH') ? %{ shift() } : ();
     my ($in_fh, $out_fh) = @_;
+    $in_fh = \*STDIN  unless ($in_fh);
+    local *myData = $self;  ## alias to avoid deref-ing overhead
+    local *myOpts = ($myData{_PARSEOPTS} ||= {});  ## get parse-options
     local $_;
 
     ## Put this stream at the top of the stack and do beginning-of-input
@@ -932,7 +1066,6 @@ sub parse_from_filehandle {
     while (defined ($textline = $tied_fh ? <$in_fh> : $in_fh->getline)) {
         $textline = $self->preprocess_line($textline, ++$nlines);
         next  unless ((defined $textline)  &&  (length $textline));
-        $_ = $paragraph;  ## save previous contents
 
         if ((! length $paragraph) && ($textline =~ /^==/)) {
             ## '==' denotes a one-line command paragraph
@@ -945,9 +1078,21 @@ sub parse_from_filehandle {
             ++$plines;
         }
 
-        ## See of this line is blank and ends the current paragraph.
+        ## See if this line is blank and ends the current paragraph.
         ## If it isnt, then keep iterating until it is.
-        next unless (($textline =~ /^\s*$/) && (length $paragraph));
+        next unless (($textline =~ /^([^\S\r\n]*)[\r\n]*$/)
+                                     && (length $paragraph));
+
+        ## Issue a warning about any non-empty blank lines
+        if (length($1) > 0 and $myOpts{'-warnings'} and ! $myData{_CUTTING}) {
+            my $errorsub = $self->errorsub();
+            my $file = $self->input_file();
+            my $errmsg = "*** WARNING: line containing nothing but whitespace".
+                         " in paragraph at line $nlines in file $file\n";
+            (ref $errorsub) and &{$errorsub}($errmsg)
+                or (defined $errorsub) and $self->$errorsub($errmsg)
+                    or  warn($errmsg);
+        }
 
         ## Now process the paragraph
         parse_paragraph($self, $paragraph, ($nlines - $plines) + 1);
@@ -1000,6 +1145,8 @@ performed). If the special output filename ">&STDERR" is given then the
 STDERR filehandle is used for output (and no open or close is
 performed). If no output filehandle is currently in use and no output
 filename is specified, then "-" is implied.
+Alternatively, an L<IO::String> object is also accepted as an output
+file handle.
 
 This method does I<not> usually need to be overridden by subclasses.
 
@@ -1009,28 +1156,36 @@ sub parse_from_file {
     my $self = shift;
     my %opts = (ref $_[0] eq 'HASH') ? %{ shift() } : ();
     my ($infile, $outfile) = @_;
-    my ($in_fh,  $out_fh)  = (undef, undef);
+    my ($in_fh,  $out_fh);
+    if ($] < 5.006) {
+      ($in_fh,  $out_fh) = (gensym(), gensym());
+    }
     my ($close_input, $close_output) = (0, 0);
     local *myData = $self;
-    local $_;
+    local *_;
 
     ## Is $infile a filename or a (possibly implied) filehandle
-    $infile  = '-'  unless ((defined $infile)  && (length $infile));
-    if (($infile  eq '-') || ($infile =~ /^<&(STDIN|0)$/i)) {
-        ## Not a filename, just a string implying STDIN
-        $myData{_INFILE} = "<standard input>";
-        $in_fh = \*STDIN;
-    }
-    elsif (ref $infile) {
+    if (defined $infile && ref $infile) {
+        if (ref($infile) =~ /^(SCALAR|ARRAY|HASH|CODE|REF)$/) {
+            croak "Input from $1 reference not supported!\n";
+        }
         ## Must be a filehandle-ref (or else assume its a ref to an object
         ## that supports the common IO read operations).
         $myData{_INFILE} = ${$infile};
         $in_fh = $infile;
     }
+    elsif (!defined($infile) || !length($infile) || ($infile eq '-')
+        || ($infile =~ /^<&(?:STDIN|0)$/i))
+    {
+        ## Not a filename, just a string implying STDIN
+        $infile ||= '-';
+        $myData{_INFILE} = "<standard input>";
+        $in_fh = \*STDIN;
+    }
     else {
         ## We have a filename, open it for reading
         $myData{_INFILE} = $infile;
-        $in_fh = FileHandle->new("< $infile")  or
+        open($in_fh, "< $infile")  or
              croak "Can't open $infile for reading: $!\n";
         $close_input = 1;
     }
@@ -1040,37 +1195,54 @@ sub parse_from_file {
     ## the entire document (but *not* if this is an included file). We
     ## determine this by seeing if the input stream stack has been set-up
     ## already
-    ## 
-    unless ((defined $outfile) && (length $outfile)) {
-        (defined $myData{_TOP_STREAM}) && ($out_fh  = $myData{_OUTPUT})
-                                       || ($outfile = '-');
-    }
-    ## Is $outfile a filename or a (possibly implied) filehandle
-    if ((defined $outfile) && (length $outfile)) {
-        if (($outfile  eq '-') || ($outfile =~ /^>&?(?:STDOUT|1)$/i)) {
-            ## Not a filename, just a string implying STDOUT
-            $myData{_OUTFILE} = "<standard output>";
-            $out_fh  = \*STDOUT;
+
+    ## Is $outfile a filename, a (possibly implied) filehandle, maybe a ref?
+    if (ref $outfile) {
+        ## we need to check for ref() first, as other checks involve reading
+        if (ref($outfile) =~ /^(ARRAY|HASH|CODE)$/) {
+            croak "Output to $1 reference not supported!\n";
         }
-        elsif ($outfile =~ /^>&(STDERR|2)$/i) {
-            ## Not a filename, just a string implying STDERR
-            $myData{_OUTFILE} = "<standard error>";
-            $out_fh  = \*STDERR;
+        elsif (ref($outfile) eq 'SCALAR') {
+#           # NOTE: IO::String isn't a part of the perl distribution,
+#           #       so probably we shouldn't support this case...
+#           require IO::String;
+#           $myData{_OUTFILE} = "$outfile";
+#           $out_fh = IO::String->new($outfile);
+            croak "Output to SCALAR reference not supported!\n";
         }
-        elsif (ref $outfile) {
+        else {
             ## Must be a filehandle-ref (or else assume its a ref to an
             ## object that supports the common IO write operations).
-            $myData{_OUTFILE} = ${$outfile};;
+            $myData{_OUTFILE} = ${$outfile};
             $out_fh = $outfile;
         }
+    }
+    elsif (!defined($outfile) || !length($outfile) || ($outfile eq '-')
+        || ($outfile =~ /^>&?(?:STDOUT|1)$/i))
+    {
+        if (defined $myData{_TOP_STREAM}) {
+            $out_fh = $myData{_OUTPUT};
+        }
         else {
-            ## We have a filename, open it for writing
-            $myData{_OUTFILE} = $outfile;
-            $out_fh = FileHandle->new("> $outfile")  or
-                 croak "Can't open $outfile for writing: $!\n";
-            $close_output = 1;
+            ## Not a filename, just a string implying STDOUT
+            $outfile ||= '-';
+            $myData{_OUTFILE} = "<standard output>";
+            $out_fh  = \*STDOUT;
         }
     }
+    elsif ($outfile =~ /^>&(STDERR|2)$/i) {
+        ## Not a filename, just a string implying STDERR
+        $myData{_OUTFILE} = "<standard error>";
+        $out_fh  = \*STDERR;
+    }
+    else {
+        ## We have a filename, open it for writing
+        $myData{_OUTFILE} = $outfile;
+        (-d $outfile) and croak "$outfile is a directory, not POD input!\n";
+        open($out_fh, "> $outfile")  or
+             croak "Can't open $outfile for writing: $!\n";
+        $close_output = 1;
+    }
 
     ## Whew! That was a lot of work to set up reasonably/robust behavior
     ## in the case of a non-filename for reading and writing. Now we just
@@ -1094,6 +1266,35 @@ instance data fields:
 
 ##---------------------------------------------------------------------------
 
+=head1 B<errorsub()>
+
+            $parser->errorsub("method_name");
+            $parser->errorsub(\&warn_user);
+            $parser->errorsub(sub { print STDERR, @_ });
+
+Specifies the method or subroutine to use when printing error messages
+about POD syntax. The supplied method/subroutine I<must> return TRUE upon
+successful printing of the message. If C<undef> is given, then the B<warn>
+builtin is used to issue error messages (this is the default behavior).
+
+            my $errorsub = $parser->errorsub()
+            my $errmsg = "This is an error message!\n"
+            (ref $errorsub) and &{$errorsub}($errmsg)
+                or (defined $errorsub) and $parser->$errorsub($errmsg)
+                    or  warn($errmsg);
+
+Returns a method name, or else a reference to the user-supplied subroutine
+used to print error messages. Returns C<undef> if the B<warn> builtin
+is used to issue error messages (this is the default behavior).
+
+=cut
+
+sub errorsub {
+   return (@_ > 1) ? ($_[0]->{_ERRORSUB} = $_[1]) : $_[0]->{_ERRORSUB};
+}
+
+##---------------------------------------------------------------------------
+
 =head1 B<cutting()>
 
             $boolean = $parser->cutting();
@@ -1115,6 +1316,58 @@ sub cutting {
 
 ##---------------------------------------------------------------------------
 
+##---------------------------------------------------------------------------
+
+=head1 B<parseopts()>
+
+When invoked with no additional arguments, B<parseopts> returns a hashtable
+of all the current parsing options.
+
+            ## See if we are parsing non-POD sections as well as POD ones
+            my %opts = $parser->parseopts();
+            $opts{'-want_nonPODs}' and print "-want_nonPODs\n";
+
+When invoked using a single string, B<parseopts> treats the string as the
+name of a parse-option and returns its corresponding value if it exists
+(returns C<undef> if it doesn't).
+
+            ## Did we ask to see '=cut' paragraphs?
+            my $want_cut = $parser->parseopts('-process_cut_cmd');
+            $want_cut and print "-process_cut_cmd\n";
+
+When invoked with multiple arguments, B<parseopts> treats them as
+key/value pairs and the specified parse-option names are set to the
+given values. Any unspecified parse-options are unaffected.
+
+            ## Set them back to the default
+            $parser->parseopts(-warnings => 0);
+
+When passed a single hash-ref, B<parseopts> uses that hash to completely
+reset the existing parse-options, all previous parse-option values
+are lost.
+
+            ## Reset all options to default 
+            $parser->parseopts( { } );
+
+See L<"PARSING OPTIONS"> for more information on the name and meaning of each
+parse-option currently recognized.
+
+=cut
+
+sub parseopts {
+   local *myData = shift;
+   local *myOpts = ($myData{_PARSEOPTS} ||= {});
+   return %myOpts  if (@_ == 0);
+   if (@_ == 1) {
+      local $_ = shift;
+      return  ref($_)  ?  $myData{_PARSEOPTS} = $_  :  $myOpts{$_};
+   }
+   my @newOpts = (%myOpts, @_);
+   $myData{_PARSEOPTS} = { @newOpts };
+}
+
+##---------------------------------------------------------------------------
+
 =head1 B<output_file()>
 
             $fname = $parser->output_file();
@@ -1358,6 +1611,167 @@ sub _pop_input_stream {
 
 #############################################################################
 
+=head1 TREE-BASED PARSING
+
+If straightforward stream-based parsing wont meet your needs (as is
+likely the case for tasks such as translating PODs into structured
+markup languages like HTML and XML) then you may need to take the
+tree-based approach. Rather than doing everything in one pass and
+calling the B<interpolate()> method to expand sequences into text, it
+may be desirable to instead create a parse-tree using the B<parse_text()>
+method to return a tree-like structure which may contain an ordered
+list of children (each of which may be a text-string, or a similar
+tree-like structure).
+
+Pay special attention to L<"METHODS FOR PARSING AND PROCESSING"> and
+to the objects described in L<Pod::InputObjects>. The former describes
+the gory details and parameters for how to customize and extend the
+parsing behavior of B<Pod::Parser>. B<Pod::InputObjects> provides
+several objects that may all be used interchangeably as parse-trees. The
+most obvious one is the B<Pod::ParseTree> object. It defines the basic
+interface and functionality that all things trying to be a POD parse-tree
+should do. A B<Pod::ParseTree> is defined such that each "node" may be a
+text-string, or a reference to another parse-tree.  Each B<Pod::Paragraph>
+object and each B<Pod::InteriorSequence> object also supports the basic
+parse-tree interface.
+
+The B<parse_text()> method takes a given paragraph of text, and
+returns a parse-tree that contains one or more children, each of which
+may be a text-string, or an InteriorSequence object. There are also
+callback-options that may be passed to B<parse_text()> to customize
+the way it expands or transforms interior-sequences, as well as the
+returned result. These callbacks can be used to create a parse-tree
+with custom-made objects (which may or may not support the parse-tree
+interface, depending on how you choose to do it).
+
+If you wish to turn an entire POD document into a parse-tree, that process
+is fairly straightforward. The B<parse_text()> method is the key to doing
+this successfully. Every paragraph-callback (i.e. the polymorphic methods
+for B<command()>, B<verbatim()>, and B<textblock()> paragraphs) takes
+a B<Pod::Paragraph> object as an argument. Each paragraph object has a
+B<parse_tree()> method that can be used to get or set a corresponding
+parse-tree. So for each of those paragraph-callback methods, simply call
+B<parse_text()> with the options you desire, and then use the returned
+parse-tree to assign to the given paragraph object.
+
+That gives you a parse-tree for each paragraph - so now all you need is
+an ordered list of paragraphs. You can maintain that yourself as a data
+element in the object/hash. The most straightforward way would be simply
+to use an array-ref, with the desired set of custom "options" for each
+invocation of B<parse_text>. Let's assume the desired option-set is
+given by the hash C<%options>. Then we might do something like the
+following:
+
+    package MyPodParserTree;
+
+    @ISA = qw( Pod::Parser );
+
+    ...
+
+    sub begin_pod {
+        my $self = shift;
+        $self->{'-paragraphs'} = [];  ## initialize paragraph list
+    }
+
+    sub command { 
+        my ($parser, $command, $paragraph, $line_num, $pod_para) = @_;
+        my $ptree = $parser->parse_text({%options}, $paragraph, ...);
+        $pod_para->parse_tree( $ptree );
+        push @{ $self->{'-paragraphs'} }, $pod_para;
+    }
+
+    sub verbatim { 
+        my ($parser, $paragraph, $line_num, $pod_para) = @_;
+        push @{ $self->{'-paragraphs'} }, $pod_para;
+    }
+
+    sub textblock { 
+        my ($parser, $paragraph, $line_num, $pod_para) = @_;
+        my $ptree = $parser->parse_text({%options}, $paragraph, ...);
+        $pod_para->parse_tree( $ptree );
+        push @{ $self->{'-paragraphs'} }, $pod_para;
+    }
+
+    ...
+
+    package main;
+    ...
+    my $parser = new MyPodParserTree(...);
+    $parser->parse_from_file(...);
+    my $paragraphs_ref = $parser->{'-paragraphs'};
+
+Of course, in this module-author's humble opinion, I'd be more inclined to
+use the existing B<Pod::ParseTree> object than a simple array. That way
+everything in it, paragraphs and sequences, all respond to the same core
+interface for all parse-tree nodes. The result would look something like:
+
+    package MyPodParserTree2;
+
+    ...
+
+    sub begin_pod {
+        my $self = shift;
+        $self->{'-ptree'} = new Pod::ParseTree;  ## initialize parse-tree
+    }
+
+    sub parse_tree {
+        ## convenience method to get/set the parse-tree for the entire POD
+        (@_ > 1)  and  $_[0]->{'-ptree'} = $_[1];
+        return $_[0]->{'-ptree'};
+    }
+
+    sub command { 
+        my ($parser, $command, $paragraph, $line_num, $pod_para) = @_;
+        my $ptree = $parser->parse_text({<<options>>}, $paragraph, ...);
+        $pod_para->parse_tree( $ptree );
+        $parser->parse_tree()->append( $pod_para );
+    }
+
+    sub verbatim { 
+        my ($parser, $paragraph, $line_num, $pod_para) = @_;
+        $parser->parse_tree()->append( $pod_para );
+    }
+
+    sub textblock { 
+        my ($parser, $paragraph, $line_num, $pod_para) = @_;
+        my $ptree = $parser->parse_text({<<options>>}, $paragraph, ...);
+        $pod_para->parse_tree( $ptree );
+        $parser->parse_tree()->append( $pod_para );
+    }
+
+    ...
+
+    package main;
+    ...
+    my $parser = new MyPodParserTree2(...);
+    $parser->parse_from_file(...);
+    my $ptree = $parser->parse_tree;
+    ...
+
+Now you have the entire POD document as one great big parse-tree. You
+can even use the B<-expand_seq> option to B<parse_text> to insert
+whole different kinds of objects. Just don't expect B<Pod::Parser>
+to know what to do with them after that. That will need to be in your
+code. Or, alternatively, you can insert any object you like so long as
+it conforms to the B<Pod::ParseTree> interface.
+
+One could use this to create subclasses of B<Pod::Paragraphs> and
+B<Pod::InteriorSequences> for specific commands (or to create your own
+custom node-types in the parse-tree) and add some kind of B<emit()>
+method to each custom node/subclass object in the tree. Then all you'd
+need to do is recursively walk the tree in the desired order, processing
+the children (most likely from left to right) by formatting them if
+they are text-strings, or by calling their B<emit()> method if they
+are objects/references.
+
+=head1 CAVEATS
+
+Please note that POD has the notion of "paragraphs": this is something
+starting I<after> a blank (read: empty) line, with the single exception
+of the file start, which is also starting a paragraph. That means that
+especially a command (e.g. C<=head1>) I<must> be preceded with a blank
+line; C<__END__> is I<not> a blank line.
+
 =head1 SEE ALSO
 
 L<Pod::InputObjects>, L<Pod::Select>
@@ -1383,6 +1797,8 @@ causing any namespace clashes due to multiple inheritance.
 
 =head1 AUTHOR
 
+Please report bugs using L<http://rt.cpan.org>.
+
 Brad Appleton E<lt>bradapp@enteract.comE<gt>
 
 Based on code for B<Pod::Text> written by
@@ -1391,3 +1807,4 @@ Tom Christiansen E<lt>tchrist@mox.perl.comE<gt>
 =cut
 
 1;
+# vim: ts=4 sw=4 et