From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

#!/usr/bin/env perl
# [[[ HEADER ]]]
use RPerl;
use strict;
our $VERSION = 0.027_000;
# [[[ CRITICS ]]]
## no critic qw(ProhibitUselessNoCritic ProhibitMagicNumbers RequireCheckedSyscalls) # USER DEFAULT 1: allow numeric values & print operator
# [[[ INCLUDES ]]]
use File::Temp qw(tempfile);
# [[[ SUBROUTINES ]]]
sub pod2cpanhtml_preprocess {
{ my string_arrayref $RETURN_TYPE };
( my string_arrayref $command_line_arguments ) = @_;
my string $input_file = $command_line_arguments->[0];
if ( not( -e $input_file ) ) {
croak 'ERROR: File ' . $input_file . ' does not exist, croaking';
}
if ( not( -r $input_file ) ) {
croak 'ERROR: File ' . $input_file . ' is not readable, croaking';
}
if ( not( -f $input_file ) ) {
croak 'ERROR: File ' . $input_file . ' is not a regular file, croaking';
}
if ( not( -T $input_file ) ) {
croak 'ERROR: File ' . $input_file . ' is (probably) not text, croaking';
}
my integer $open_success = open my filehandleref $INPUT_FILEHANDLE, '<', $input_file;
if ( not $open_success ) {
croak 'ERROR: Failed to open file ' . $input_file . ' for reading, croaking';
}
my string_arrayref $file_lines = [];
my string $file_line_previous = undef;
my string $file_line_next = undef;
my integer $file_line_number = 0;
while ( my string $file_line = <$INPUT_FILEHANDLE> ) {
$file_line_number++;
# enable HTML newline <br> from null POD X<br>
if ($file_line =~ s/X<br>/<br>/gxms) {
$file_line_next = <$INPUT_FILEHANDLE>;
$file_line_number++;
if ($file_line_next ne "\n") { croak 'ERROR: X<br> tag followed by non-blank line ' . q{'} . $file_line_next . q{'} . ' on input file line ' . $file_line_number . ', croaking'; }
$file_line_next = undef; # discard extra line after X<br> tag
}
# disable <code>...</code> tags from null POD X<noncode>...X</noncode>
# if ($file_line =~ s/X<noncode>/<noncode>/gxms) {
if ($file_line =~ s/=for\ rperl\ X<noncode>/<noncode>/gxms) {
$file_line_next = <$INPUT_FILEHANDLE>;
$file_line_number++;
if ($file_line_next ne "\n") { croak 'ERROR: X<noncode> tag followed by non-blank line ' . q{'} . $file_line_next . q{'} . ' on input file line ' . $file_line_number . ', croaking'; }
}
if ($file_line =~ s/=for\ rperl\ X<\/noncode>/<\/noncode>/gxms) {
if ($file_line_previous ne "\n") { croak 'ERROR: X</noncode> tag preceeded by non-blank line ' . q{'} . $file_line_previous . q{'} . ' on input file line ' . $file_line_number . ', croaking'; }
}
# enable HTML underline <u>...</u> from null POD X<u>...X</u>; DEPRECATED IN FAVOR OF =for html <u>...</u>
# $file_line =~ s/X<u>/<u>/gxms;
# $file_line =~ s/X<\/u>/<\/u>/gxms;
push @{$file_lines}, $file_line;
if (defined $file_line_next) {
push @{$file_lines}, $file_line_next;
$file_line_next = undef;
}
$file_line_previous = $file_line;
}
if ( not close $INPUT_FILEHANDLE ) {
croak 'ERROR: Failed to close file ' . $input_file . ' after reading, croaking';
}
# print 'in pod2cpanhtml_preprocess(), about to return $file_lines = ' . "\n" . (join q{}, @{$file_lines});
return $file_lines;
}
sub pod2cpanhtml_process {
{ my string_arrayref $RETURN_TYPE };
( my string_arrayref $file_lines ) = @_;
my filehandleref $TEMP_FILE_HANDLE;
my string $temp_file_name;
# ( $TEMP_FILE_HANDLE, $temp_file_name ) = tempfile( 'tempfileXXXX', SUFFIX => '.tmp' );
( $TEMP_FILE_HANDLE, $temp_file_name ) = tempfile( 'tempfileXXXX', SUFFIX => '.tmp', UNLINK => 1, TMPDIR => 1 );
my string $file_lines_joined = join q{}, @{$file_lines};
print {$TEMP_FILE_HANDLE} $file_lines_joined
or croak( 'Attempting to save new file ' . $temp_file_name . ', cannot write to file, croaking:' . $OS_ERROR );
close $TEMP_FILE_HANDLE
or croak( 'Attempting to save new file ' . $temp_file_name . ', cannot close file, croaking:' . $OS_ERROR );
# print 'in pod2cpanhtml_process(), have $temp_file_name = ' . $temp_file_name . "\n";
my App::Pod2CpanHtml $parser = App::Pod2CpanHtml->new();
my string $file_lines_modified;
$parser->output_string( \$file_lines_modified );
$parser->parse_file($temp_file_name);
$file_lines = [ ( split /\n/xms, $file_lines_modified ) ];
# print 'in pod2cpanhtml_process(), about to return $file_lines = ' . "\n" . ( join "\n", @{$file_lines} );
return $file_lines;
}
sub pod2cpanhtml_postprocess {
{ my string_arrayref $RETURN_TYPE };
( my string_arrayref $file_lines ) = @_;
my string_arrayref $file_lines_modified = [];
my boolean $inside_edition = 0; # no, not Bill O'Reilly
my boolean $inside_toc = 0;
my boolean $need_check_close_ul = 0;
my string $file_line_saved = undef;
my boolean $inside_noncode = 0;
push @{$file_lines_modified}, '<!DOCTYPE html>'; # why isn't there already a DOCTYPE tag added by one of the other HTML generators? :P
foreach my string $file_line ( @{$file_lines} ) {
# CPAN only, not MetaCPAN: help inline code tags stand out more, highlighted background matching the block-indented code
# if ($file_line eq '</head>') { push @{$file_lines_modified}, '<style> code { background: #eeeeee; } </style>'; } # no outline
# if ($file_line eq '</head>') { push @{$file_lines_modified}, '<style> code { background: #eeeeee; border: 1px solid #888888; } </style>'; } # yes outline
# hide full TOC until all content is loaded, to avoid delayed-collapse issue
if ($file_line eq q{</head>}) { push @{$file_lines_modified}, q{<style>.wait_for_javascript { display: none; } </style>}, q{}; }
if ($file_line eq q{<a name='___top' class='dummyTopAnchor' ></a>}) { push @{$file_lines_modified}, q{<div id="full_table_of_contents" class="hide_full_table_of_contents wait_for_javascript">}, q{}; }
if ($file_line eq q{<div id="scoped-content"><style type="text/css" scoped>}) { push @{$file_lines_modified}, q{}, q{</div> <!-- id="full_table_of_contents" -->}, q{}; }
if ($file_line eq q{</body></html>}) { push @{$file_lines_modified}, q{}, q{<script> document.getElementById('full_table_of_contents').className = 'hide_full_table_of_contents'; </script>}, q{}; }
# Edition: insert Learning.pm $VERSION & date
if ($file_line eq 'name="EDITION"') {
# print {*STDERR} 'have $file_line = ' . $file_line . "\n";
$inside_edition = 1;
push @{$file_lines_modified}, $file_line;
next;
}
elsif ($inside_edition) {
if ($file_line eq '<p>&#60;br&#62;</p>') {
# print {*STDERR} 'have $file_line = ' . $file_line . "\n";
$file_line = '<p><br></p>';
push @{$file_lines_modified}, ('<p>Automatically Generated From <a href="https://github.com/wbraswell/rperl/blob/master/lib/RPerl/Learning.pm"><i>RPerl/Learning.pm</i></a> v' . number_to_string($RPerl::Learning::VERSION) . '<br>' . "\n");
push @{$file_lines_modified}, ('Using <a href="https://github.com/wbraswell/rperl/blob/master/script/development/pod2rperlhtml.pl"><i>pod2rperlhtml.pl</i></a> v' . number_to_string($VERSION) . '<br>' . "\n");
push @{$file_lines_modified}, (time2str('On %A, %B %o, %Y at %l:%M%P %Z', time) . '<br>' . "\n");
# push @{$file_lines_modified}, (time2str('%Y%m%ld %Y.%j %H%M.%S', time) . '</p>' . "\n"); # Longdate & Stardate
$inside_edition = 0;
}
push @{$file_lines_modified}, $file_line;
next;
}
if ($file_line eq '<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" >') {
push @{$file_lines_modified}, q{<meta name="viewport" content="width=device-width, initial-scale=1">}; # enable mobile device responsive behavior
push @{$file_lines_modified}, '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'; # MetaCPAN & SyntaxHighlighter: enable CSS & Javascript
next;
}
if ($file_line eq '<link rel="stylesheet" type="text/css" title="pod_stylesheet" href="http://search.cpan.org/s/style.css">') {
# push @{$file_lines_modified}, '<link href="https://metacpan.org/_assets/d9ff6fc28efe349c1ffc17c397bd95ca.css" rel="stylesheet" type="text/css">'; # live remote copy
# push @{$file_lines_modified}, '<script src="https://metacpan.org/_assets/a869e92bd8b69760f4c3b0a85075f0f9.js" type="text/javascript"></script>'; # live remote copy
# push @{$file_lines_modified}, '<link href="metacpan.css" rel="stylesheet" type="text/css">'; # 20160101 local copy
# push @{$file_lines_modified}, '<script src="metacpan.js" type="text/javascript"></script>'; # 20160101 local copy
push @{$file_lines_modified}, '<link href="stylesheets/metacpan_rperl.css" rel="stylesheet" type="text/css">'; # modified local copy
push @{$file_lines_modified}, '<script src="javascripts/metacpan_rperl.js" type="text/javascript"></script>'; # modified local copy
next;
}
# MetaCPAN & SyntaxHighlighter: enable syntax highlighting div
if ($file_line =~ m/<body class='pod'>/) {
push @{$file_lines_modified}, '<body>';
push @{$file_lines_modified}, '<div class="pod content anchors">';
next;
}
elsif ($file_line =~ m/<\/body>/) {
push @{$file_lines_modified}, '</div> <!-- END class="pod content anchors": SyntaxHighlighter applied to code within this div -->';
push @{$file_lines_modified}, $file_line;
next;
}
if ($file_line =~ m/^\s+generated\sby\sPod::Simple::HTML/) {
push @{$file_lines_modified}, ' re-generated by pod2rperlhtml.pl v' . number_to_string($VERSION) . ',';
}
# MetaCPAN: TOC changes
if ($file_line eq q{<div class='indexgroup'>}) {
# delete <div class='indexgroup'> before TOC
$inside_toc = 1;
next;
}
elsif ($inside_toc) {
if ($file_line eq q{<ul class='indexList indexList1'>}) {
# change <ul class='indexList indexList1'> to <ul id="index">
push @{$file_lines_modified}, '<ul id="index">';
next;
}
elsif ($file_line eq '</div>') {
# delete </div> after TOC
$inside_toc = 0;
next;
}
# change <li class='FOO'><a href to <li><a href
$file_line =~ s/<li\s+class\=\'.*\'><a\s+href/<li><a href/gxms;
# change </ul> to </ul></li>
$file_line =~ s/<\/ul>/<\/ul><\/li>/gxms;
# change <ul FOO> to <ul>
$file_line =~ s/<ul.+>/<ul>/gxms;
=NEED FIX
# change </a> to </a></li> EXCEPT when followed by </ul>
if ($file_line =~ m/<\/a>/) {
$need_check_close_ul = 1;
$file_line_saved = $file_line;
next;
}
elsif ($need_check_close_ul) {
if ($file_line !~ m/^\s*<\/ul>\s*$/) {
$file_line_saved =~ s/<\/a>/<\/a><\/li>/gxms;
}
push @{$file_lines_modified}, $file_line_saved;
$need_check_close_ul = 0;
$file_line_saved = undef;
}
=cut
}
# enable HTML newline <br> from mangled HTML literals
# $file_line =~ s/&lt;br&gt;/<br>/gxms; # pod2html
$file_line =~ s/&\#60;br&\#62;/<br>/gxms; # pod2cpanhtml
# disable HTML <code>...</code> from mangled HTML <noncode>...</noncode> literals
if ($file_line eq '<p>&#60;noncode&#62;</p>') {
$inside_noncode = 1;
next;
}
elsif ($inside_noncode) {
if ($file_line eq '<p>&#60;/noncode&#62;</p>') {
$inside_noncode = 0;
next;
}
}
else {
# MetaCPAN & SyntaxHighlighter: enable indented block code formatting
$file_line =~ s/<pre>/<pre><code>/gxms;
$file_line =~ s/<\/pre>/<\/code><\/pre>/gxms;
}
# enable HTML underline <u>...</u> from mangled HTML literals; DEPRECATED IN FAVOR OF =for html <u>...</u>
# $file_line =~ s/&\#60;u&\#62;/<u>/gxms; # pod2cpanhtml
# $file_line =~ s/&\#60;\/u&\#62;/<\/u>/gxms; # pod2cpanhtml
# disable HTML underline tags in page title
$file_line =~ s/<title><u>(.*)<\/u><\/title>/<title>$1<\/title>/gxms;
# strip extra newline between list items
$file_line =~ s/<li><p>(.*)<\/p>/<li>$1/gxms;
push @{$file_lines_modified}, $file_line;
}
return $file_lines_modified;
}
# [[[ OPERATIONS ]]]
my string_arrayref $file_lines;
$file_lines = pod2cpanhtml_preprocess( [@ARGV] );
$file_lines = pod2cpanhtml_process($file_lines);
$file_lines = pod2cpanhtml_postprocess($file_lines);
print join "\n", @{$file_lines};