package Pod::Diff; use Exporter; @ISA = qw(Exporter); @EXPORT = qw(pod_diff_files pod_diff_print_stats); use strict; # Set this for strict checking, i.e.: # * over commands must match # * multiple spaces within non-verbatim paragraphs must match my $strict = 0; # Statistics collected my $total_runs = 0; my $total_paras = 0; my $total_diffs = 0; my $total_resync_fails = 0; # The default formatting routine for a difference sub _pod_diff_fmt { my($i, $text1, $text2, $line1, $line2) = @_; # Find the first character which differs # Is there a better way than this? my $first = 0; $first++ while substr($text1, $first, 1) eq substr($text2, $first, 1); $first++; return join("\n", "*** paragraph $i - character $first ***", "--- $line1 ---", $text1, "--- $line2 ---", $text2); } # Parse a pod array into a list of paragraphs. # Each paragraph has the following fields: # # * TEXT - the paragraph text # * LINE - the starting line number sub _pod_parse_paras { my($array) = @_; my(@list); my $lineno = 0; my $text = ''; my $tab_size = 8; my $line; for $line (@$array) { $lineno++; # Trim trailing whitespace and convert tabs to spaces $line =~ s/\s+$//; 1 while $line =~ s/\t+/' ' x (length($&) * $tab_size - length($`) % $tab_size)/e; # Build and store the paragraphs if ($line =~ /^$/) { if ($text ne '') { push(@list, {TEXT => $text, LINE => $lineno}); $text = ''; } } elsif ($text eq '') { $text = $line; } else { if ($text =~ /^\s/) { $text .= "\n$line"; } else { $text .= " $line"; } } } # Save the last paragraph, if necessary if ($text ne '') { push(@list, {TEXT => $text, LINE => $lineno}); } # Return result return @list; } # Remove redundant escapes from a paragraph sub _fix_escapes { my($text) = @_; # For verbatim, leave things alone return $text if $text =~ /^ /; my $result = ''; my $phrase = ''; my @nested = (); my $tag = ''; while ($text ne '') { # A > without a proceeding < may be a sequence end marker if ($text =~ /^([^<>]*)\>/) { $text = $'; if (@nested) { $tag = pop(@nested); $phrase = $1; if ($tag eq 'E' && ($phrase eq 'gt' || $phrase eq 'lt')) { # The escape isn't necessary unless: # * the preceding character is [A-Z] for <, or # * it's inside an interior sequence for >, or if ($phrase eq 'gt' && scalar @nested > 0 || $phrase eq 'lt' && $result =~ /[A-Z]E\<$/) { $result .= "$phrase>"; } else { $result =~ s/E\<$//; $result .= $phrase eq 'gt' ? '>' : '<'; } } else { $result .= $` . $&; } } else { $result .= $` . $&; } } # A sequence which may have something nested elsif ($text =~ /([A-Z])\; chop(@pod1); close FILE1; # Load the pod from the second file unless (open(FILE2, $file2)) { warn "unable to open '$file2': $!\n"; return (); } my @pod2 = ; chop(@pod2); close FILE2; # Diff the arrays return pod_diff_arrays(\@pod1, \@pod2, $formatter); } sub pod_diff_print_stats { my($strm) = @_; print $strm "*** SUMMARY ***\n"; print $strm "Total files: ", $total_runs, "\n"; print $strm "Total paras: ", $total_paras, "\n"; print $strm "Total diffs: ", $total_diffs, "\n"; print $strm "Total resync failures: ", $total_resync_fails, "\n"; if ($total_paras) { printf $strm "PERCENT OK: %.2f%%\n", ($total_paras - $total_diffs)/$total_paras * 100; } } # package return value 1;