diff options
author | Russ Allbery <rra@cpan.org> | 2021-12-23 21:57:43 -0800 |
---|---|---|
committer | Russ Allbery <rra@cpan.org> | 2021-12-23 21:57:43 -0800 |
commit | 74c5d402923e0d4085200a38cfddeea8af16f341 (patch) | |
tree | 3fa4aa45d6510ce64f6c9f0443c3f4a6779e2b4e /lib | |
parent | 75c60cbc6a3c81f1991fac42966e13fa3cc77b51 (diff) |
Move utility functions to new module
Move is_newer, print_checked, and print_fh into a new
App::DocKnot::Util module to share them between the multiple modules
that use them.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/App/DocKnot/Spin.pm | 88 | ||||
-rw-r--r-- | lib/App/DocKnot/Spin/Pointer.pm | 43 | ||||
-rw-r--r-- | lib/App/DocKnot/Spin/RSS.pm | 65 | ||||
-rw-r--r-- | lib/App/DocKnot/Spin/Thread.pm | 25 | ||||
-rw-r--r-- | lib/App/DocKnot/Util.pm | 172 |
5 files changed, 226 insertions, 167 deletions
diff --git a/lib/App/DocKnot/Spin.pm b/lib/App/DocKnot/Spin.pm index 27b57e0..d1fe6f0 100644 --- a/lib/App/DocKnot/Spin.pm +++ b/lib/App/DocKnot/Spin.pm @@ -22,6 +22,7 @@ use App::DocKnot::Spin::RSS; use App::DocKnot::Spin::Sitemap; use App::DocKnot::Spin::Thread; use App::DocKnot::Spin::Versions; +use App::DocKnot::Util qw(is_newer print_checked print_fh); use Carp qw(croak); use Cwd qw(getcwd realpath); use File::Basename qw(fileparse); @@ -29,8 +30,7 @@ use File::Copy qw(copy); use File::Find qw(find finddepth); use File::Spec (); use Git::Repository (); -use List::SomeUtils qw(all); -use IPC::System::Simple qw(capture systemx); +use IPC::System::Simple qw(capture); use Pod::Thread 3.00 (); use POSIX qw(strftime); @@ -47,51 +47,9 @@ my @EXCLUDES = ( my $URL = 'https://www.eyrie.org/~eagle/software/web/'; ############################################################################## -# Utility functions -############################################################################## - -# Check if a file, which may not exist, is newer than another list of files. -# -# $file - File whose timestamp to compare -# @others - Other files to compare against -# -# Returns: True if $file exists and is newer than @others, false otherwise -sub _is_newer { - my ($file, @others) = @_; - return if !-e $file; - my $file_mtime = (stat($file))[9]; - my @others_mtimes = map { (stat)[9] } @others; - return all { $file_mtime >= $_ } @others_mtimes; -} - -############################################################################## # Output ############################################################################## -# print with error checking. autodie unfortunately can't help us because -# print can't be prototyped and hence can't be overridden. -sub _print_checked { - my (@args) = @_; - print @args or croak('print failed'); - return; -} - -# print with error checking and an explicit file handle. autodie -# unfortunately can't help us because print can't be prototyped and -# hence can't be overridden. -# -# $fh - Output file handle -# $file - File name for error reporting -# @args - Remaining arguments to print -# -# Returns: undef -# Throws: Text exception on output failure -sub _print_fh { - my ($fh, $file, @args) = @_; - print {$fh} @args or croak("cannot write to $file: $!"); - return; -} - # Build te page footer, which consists of the navigation links, the regular # signature, and the last modified date. # @@ -188,15 +146,15 @@ sub _write_converter_output { if ($self->{sitemap} && $line =~ m{ \A </head> }xmsi) { my @links = $self->{sitemap}->links($page); if (@links) { - _print_fh($out_fh, $output, @links); + print_fh($out_fh, $output, @links); } } - _print_fh($out_fh, $output, $line); + print_fh($out_fh, $output, $line); if ($line =~ m{ <body }xmsi) { if ($self->{sitemap}) { my @navbar = $self->{sitemap}->navbar($page); if (@navbar) { - _print_fh($out_fh, $output, @navbar); + print_fh($out_fh, $output, @navbar); } } last; @@ -210,13 +168,13 @@ sub _write_converter_output { my $line; while (defined($line = shift($page_ref->@*))) { last if $line =~ m{ </body> }xmsi; - _print_fh($out_fh, $output, $line); + print_fh($out_fh, $output, $line); } # Add the footer and finish with the output. - _print_fh($out_fh, $output, $footer->($blurb, $docid)); + print_fh($out_fh, $output, $footer->($blurb, $docid)); if (defined($line)) { - _print_fh($out_fh, $output, $line, $page_ref->@*); + print_fh($out_fh, $output, $line, $page_ref->@*); } close($out_fh); return; @@ -421,7 +379,7 @@ sub _process_file { if (-e $output && !-d $output) { die "cannot replace $output with a directory\n"; } elsif (!-d $output) { - _print_checked("Creating $shortout\n"); + print_checked("Creating $shortout\n"); mkdir($output, 0755); } my $rss_path = File::Spec->catfile($file, '.rss'); @@ -433,7 +391,7 @@ sub _process_file { $shortout =~ s{ [.] spin \z }{.html}xms; $self->{generated}{$output} = 1; if ($self->{pointer}->is_out_of_date($input, $output)) { - _print_checked("Converting $shortout\n"); + print_checked("Converting $shortout\n"); $self->{pointer}->spin_pointer($input, $output); } } elsif ($file =~ m{ [.] th \z }xms) { @@ -447,13 +405,13 @@ sub _process_file { my $relative = $input; $relative =~ s{ ^ \Q$self->{source}\E / }{}xms; my $time = $self->{versions}->latest_release($relative); - return if _is_newer($output, $file) && (stat($output))[9] >= $time; + return if is_newer($output, $file) && (stat($output))[9] >= $time; } else { - return if _is_newer($output, $file); + return if is_newer($output, $file); } # The output file is not newer. Respin it. - _print_checked("Spinning $shortout\n"); + print_checked("Spinning $shortout\n"); $self->{thread}->spin_thread_file($input, $output); } else { my ($extension) = ($file =~ m{ [.] ([^.]+) \z }xms); @@ -463,13 +421,13 @@ sub _process_file { $shortout =~ s{ [.] \Q$extension\E \z }{.html}xms; $self->{generated}{$output} = 1; my ($source, $options, $style) = $self->_read_pointer($input); - return if _is_newer($output, $input, $source); - _print_checked("Running $name for $shortout\n"); + return if is_newer($output, $input, $source); + print_checked("Running $name for $shortout\n"); $self->$sub($source, $output, $options, $style); } else { $self->{generated}{$output} = 1; - return if _is_newer($output, $file); - _print_checked("Updating $shortout\n"); + return if is_newer($output, $file); + print_checked("Updating $shortout\n"); copy($file, $output) or die "copy of $input to $output failed: $!\n"; } @@ -492,7 +450,7 @@ sub _delete_files { return if $self->{generated}{$file}; my $shortfile = $file; $shortfile =~ s{ ^ \Q$self->{output}\E }{...}xms; - _print_checked("Deleting $shortfile\n"); + print_checked("Deleting $shortfile\n"); if (-d $file) { rmdir($file); } else { @@ -564,7 +522,7 @@ sub spin { # Canonicalize and check output. if (!-d $output) { - _print_checked("Creating $output\n"); + print_checked("Creating $output\n"); mkdir($output, 0755); } $output = realpath($output) or die "cannot canonicalize $output: $!\n"; @@ -650,10 +608,10 @@ App::DocKnot::Spin - Static site builder supporting thread macro language =head1 REQUIREMENTS -Perl 5.24 or later and the modules Git::Repository, Image::Size, and -Pod::Thread, all of which are available from CPAN. Also expects to find -B<faq2html>, B<cvs2xhtml>, and B<cl2xhtml> on the user's PATH to convert -certain types of files. +Perl 5.24 or later and the modules Git::Repository, Image::Size, +List::SomeUtils, and Pod::Thread, all of which are available from CPAN. Also +expects to find B<faq2html>, B<cvs2xhtml>, and B<cl2xhtml> on the user's PATH +to convert certain types of files. =head1 DESCRIPTION diff --git a/lib/App/DocKnot/Spin/Pointer.pm b/lib/App/DocKnot/Spin/Pointer.pm index 969660f..2e1c67d 100644 --- a/lib/App/DocKnot/Spin/Pointer.pm +++ b/lib/App/DocKnot/Spin/Pointer.pm @@ -18,6 +18,7 @@ use parent qw(App::DocKnot); use warnings; use App::DocKnot::Config; +use App::DocKnot::Util qw(is_newer print_fh); use Carp qw(croak); use Encode qw(decode encode); use File::BaseDir qw(config_files); @@ -32,40 +33,6 @@ use YAML::XS (); my $URL = 'https://www.eyrie.org/~eagle/software/web/'; ############################################################################## -# Utility functions -############################################################################## - -# Check if a file, which may not exist, is newer than another list of files. -# -# $file - File whose timestamp to compare -# @others - Other files to compare against -# -# Returns: True if $file exists and is newer than @others, false otherwise -sub _is_newer { - my ($file, @others) = @_; - return if !-e $file; - my $file_mtime = (stat($file))[9]; - my @others_mtimes = map { (stat)[9] } @others; - return all { $file_mtime >= $_ } @others_mtimes; -} - -# print with error checking and an explicit file handle. autodie -# unfortunately can't help us because print can't be prototyped and hence -# can't be overridden. -# -# $fh - Output file handle -# $file - File name for error reporting -# @args - Remaining arguments to print -# -# Returns: undef -# Throws: Text exception on output failure -sub _print_fh { - my ($fh, $file, @args) = @_; - print {$fh} @args or croak("cannot write to $file: $!"); - return; -} - -############################################################################## # Format conversions ############################################################################## @@ -127,7 +94,7 @@ sub _spin_markdown { # Write the result to the output file. open(my $outfh, '>', $output); - _print_fh($outfh, $output, encode('utf-8', $result)); + print_fh($outfh, $output, encode('utf-8', $result)); close($outfh); return; } @@ -190,7 +157,7 @@ sub is_out_of_date { if (!-e $data_ref->{path}) { die "$pointer: path $data_ref->{path} does not exist\n"; } - return !_is_newer($output, $pointer, $data_ref->{path}); + return !is_newer($output, $pointer, $data_ref->{path}); } # Process a given pointer file. @@ -243,8 +210,8 @@ App::DocKnot::Spin::Pointer - Generate HTML from a pointer to an external file =head1 REQUIREMENTS -Perl 5.24 or later and the modules File::ShareDir, Kwalify, and YAML::XS, all -of which are available from CPAN. +Perl 5.24 or later and the modules File::ShareDir, Kwalify, List::SomeUtils, +and YAML::XS, all of which are available from CPAN. =head1 DESCRIPTION diff --git a/lib/App/DocKnot/Spin/RSS.pm b/lib/App/DocKnot/Spin/RSS.pm index dff4665..be5b105 100644 --- a/lib/App/DocKnot/Spin/RSS.pm +++ b/lib/App/DocKnot/Spin/RSS.pm @@ -17,6 +17,7 @@ use warnings; use App::DocKnot; use App::DocKnot::Spin::Thread; +use App::DocKnot::Util qw(print_checked print_fh); use Cwd qw(getcwd); use Date::Language (); use Date::Parse qw(str2time); @@ -28,30 +29,6 @@ use POSIX qw(strftime); # Utility functions ############################################################################## -# print with error checking. autodie unfortunately can't help us because -# print can't be prototyped and hence can't be overridden. -sub _print_checked { - my (@args) = @_; - print @args or croak('print failed'); - return; -} - -# print with error checking and an explicit file handle. autodie -# unfortunately can't help us because print can't be prototyped and hence -# can't be overridden. -# -# $fh - Output file handle -# $file - File name for error reporting -# @args - Remaining arguments to print -# -# Returns: undef -# Throws: Text exception on output failure -sub _print_fh { - my ($fh, $file, @args) = @_; - print {$fh} @args or croak("cannot write to $file: $!"); - return; -} - # Escapes &, <, and > characters for HTML or XML output. # # $string - Input string @@ -415,7 +392,7 @@ sub _rss_output { } # Output the RSS header. - _print_fh($fh, $file, <<"EOC"); + print_fh($fh, $file, <<"EOC"); <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <channel> @@ -430,14 +407,14 @@ EOC if ($metadata_ref->{'rss-base'}) { my ($name) = fileparse($file); my $url = $metadata_ref->{'rss-base'} . $name; - _print_fh( + print_fh( $fh, $file, qq{ <atom:link href="$url" rel="self"\n}, qq{ type="application/rss+xml" />\n}, ); } - _print_fh($fh, $file, "\n"); + print_fh($fh, $file, "\n"); # Output each entry, formatting the contents of the entry as we go. for my $entry_ref ($entries_ref->@*) { @@ -470,7 +447,7 @@ EOC } # Output the entry. - _print_fh( + print_fh( $fh, $file, " <item>\n", @@ -486,7 +463,7 @@ EOC } # Close the RSS structure. - _print_fh($fh, $file, " </channel>\n</rss>\n"); + print_fh($fh, $file, " </channel>\n</rss>\n"); return; } @@ -505,9 +482,9 @@ sub _thread_output { # Page prefix. if ($metadata_ref->{'thread-prefix'}) { - _print_fh($fh, $file, $metadata_ref->{'thread-prefix'}, "\n"); + print_fh($fh, $file, $metadata_ref->{'thread-prefix'}, "\n"); } else { - _print_fh( + print_fh( $fh, $file, "\\heading[Recent Changes][indent]\n\n", @@ -522,13 +499,13 @@ sub _thread_output { # Put headings before each month. if (!$last_month || $month ne $last_month) { - _print_fh($fh, $file, "\\h2[$month]\n\n"); + print_fh($fh, $file, "\\h2[$month]\n\n"); $last_month = $month; } # Format each entry. my $date = strftime('%Y-%m-%d', localtime($entry_ref->{date})); - _print_fh( + print_fh( $fh, $file, "\\desc[$date \\entity[mdash]\n", @@ -538,11 +515,11 @@ sub _thread_output { my $description = $entry_ref->{description}; $description =~ s{ ^ }{ }xmsg; $description =~ s{ \\ }{\\\\}xmsg; - _print_fh($fh, $file, $description, "]\n\n"); + print_fh($fh, $file, $description, "]\n\n"); } # Print out the end of the page. - _print_fh($fh, $file, "\\signature\n"); + print_fh($fh, $file, "\\signature\n"); return; } @@ -651,7 +628,7 @@ sub _index_output { # Output the prefix. if ($metadata_ref->{'index-prefix'}) { - _print_fh($fh, $file, $metadata_ref->{'index-prefix'}, "\n"); + print_fh($fh, $file, $metadata_ref->{'index-prefix'}, "\n"); } # Output each entry. @@ -681,7 +658,7 @@ sub _index_output { }{$1 . _relative_url($2, $metadata_ref->{'index-base'}) . ']' }xmsge; # Print out the entry. - _print_fh( + print_fh( $fh, $file, "\\h2[$day: $entry_ref->{title}]\n\n", @@ -694,9 +671,9 @@ sub _index_output { # Print out the end of the page. if ($metadata_ref->{'index-suffix'}) { - _print_fh($fh, $file, $metadata_ref->{'index-suffix'}, "\n"); + print_fh($fh, $file, $metadata_ref->{'index-suffix'}, "\n"); } - _print_fh($fh, $file, "\\signature\n"); + print_fh($fh, $file, "\\signature\n"); return; } @@ -766,7 +743,7 @@ sub generate { # Write the output. if ($format eq 'thread') { - _print_checked("Generating thread file $prettyfile\n"); + print_checked("Generating thread file $prettyfile\n"); open(my $fh, '>', $path); $self->_thread_output($fh, $path, $metadata_ref, \@entries); close($fh); @@ -774,7 +751,7 @@ sub generate { if (scalar(@entries) > $metadata_ref->{recent}) { splice(@entries, $metadata_ref->{recent}); } - _print_checked("Generating RSS file $prettyfile\n"); + print_checked("Generating RSS file $prettyfile\n"); open(my $fh, '>', $path); $self->_rss_output($fh, $path, $metadata_ref, \@entries); close($fh); @@ -782,7 +759,7 @@ sub generate { if (scalar(@entries) > $metadata_ref->{recent}) { splice(@entries, $metadata_ref->{recent}); } - _print_checked("Generating index file $prettyfile\n"); + print_checked("Generating index file $prettyfile\n"); open(my $fh, '>', $path); $self->_index_output($fh, $path, $metadata_ref, \@entries); close($fh); @@ -816,8 +793,8 @@ App::DocKnot::Spin::RSS - Generate RSS and thread from a feed description file =head1 REQUIREMENTS Perl 5.006 or later and the modules Date::Language, Date::Parse (both part of -the TimeDate distribution), and Perl6::Slurp, both of which are available from -CPAN. +the TimeDate distribution), List::SomeUtils, and Perl6::Slurp, both of which +are available from CPAN. =head1 DESCRIPTION diff --git a/lib/App/DocKnot/Spin/Thread.pm b/lib/App/DocKnot/Spin/Thread.pm index 6669286..93f6908 100644 --- a/lib/App/DocKnot/Spin/Thread.pm +++ b/lib/App/DocKnot/Spin/Thread.pm @@ -16,6 +16,7 @@ use autodie; use warnings; use App::DocKnot; +use App::DocKnot::Util qw(print_fh); use Cwd qw(getcwd realpath); use File::Basename qw(fileparse); use File::Spec (); @@ -104,22 +105,6 @@ sub _read_file { return $text; } -# print with error checking and an explicit file handle. autodie -# unfortunately can't help us because print can't be prototyped and hence -# can't be overridden. -# -# $fh - Output file handle -# $file - File name for error reporting -# @args - Remaining arguments to print -# -# Returns: undef -# Throws: Text exception on output failure -sub _print_fh { - my ($fh, $file, @args) = @_; - print {$fh} @args or croak("cannot write to $file: $!"); - return; -} - # Sends something to the output file with special handling of whitespace for # more readable HTML output. # @@ -159,7 +144,7 @@ sub _output { } # Send the results to the output file. - _print_fh($self->{out_fh}, $self->{out_path}, $output); + print_fh($self->{out_fh}, $self->{out_path}, $output); return; } @@ -688,7 +673,7 @@ sub _parse_document { } # Close open tags and print any deferred whitespace. - _print_fh($out_fh, $out_path, $self->_block_end(), $self->{space}); + print_fh($out_fh, $out_path, $self->_block_end(), $self->{space}); return; } @@ -1520,8 +1505,8 @@ App::DocKnot::Spin::Thread - Generate HTML from the macro language thread =head1 REQUIREMENTS -Perl 5.24 or later and the modules Git::Repository and Image::Size, both of -which are available from CPAN. +Perl 5.24 or later and the modules Git::Repository, Image::Size, and +List::SomeUtils, all of which are available from CPAN. =head1 DESCRIPTION diff --git a/lib/App/DocKnot/Util.pm b/lib/App/DocKnot/Util.pm new file mode 100644 index 0000000..a72b37a --- /dev/null +++ b/lib/App/DocKnot/Util.pm @@ -0,0 +1,172 @@ +# Shared utility functions for other DocKnot modules. +# +# A collection of random utility functions that are used by more than one +# DocKnot module but don't make sense as App::DocKnot methods. +# +# SPDX-License-Identifier: MIT + +############################################################################## +# Modules and declarations +############################################################################## + +package App::DocKnot::Util 5.00; + +use 5.024; +use autodie; +use warnings; + +use Carp qw(croak); +use Exporter qw(import); +use List::SomeUtils qw(all); + +our @EXPORT_OK = qw(is_newer print_checked print_fh); + +############################################################################## +# Public interface +############################################################################## + +# Check if a file, which may not exist, is newer than another list of files. +# +# $file - File whose timestamp to compare +# @others - Other files to compare against +# +# Returns: True if $file exists and is newer than @others, false otherwise +sub is_newer { + my ($file, @others) = @_; + return if !-e $file; + my $file_mtime = (stat($file))[9]; + my @others_mtimes = map { (stat)[9] } @others; + return all { $file_mtime >= $_ } @others_mtimes; +} + +# print with error checking. autodie unfortunately can't help us because +# print can't be prototyped and hence can't be overridden. +# +# @args - Arguments to print to stdout +# +# Returns: undef +# Throws: Text exception on output failure +sub print_checked { + my (@args) = @_; + print @args or croak('print failed'); + return; +} + +# print with error checking and an explicit file handle. autodie +# unfortunately can't help us because print can't be prototyped and +# hence can't be overridden. +# +# $fh - Output file handle +# $file - File name for error reporting +# @args - Remaining arguments to print +# +# Returns: undef +# Throws: Text exception on output failure +sub print_fh { + my ($fh, $file, @args) = @_; + print {$fh} @args or croak("cannot write to $file: $!"); + return; +} + +############################################################################## +# Module return value and documentation +############################################################################## + +1; +__END__ + +=for stopwords +Allbery DocKnot MERCHANTABILITY NONINFRINGEMENT sublicense FH autodie + +=head1 NAME + +App::DocKnot::Util - Shared utility functions for other DocKnot modules + +=head1 SYNOPSIS + + use App::DocKnot::Util qw(is_newer print_checked print_fh); + + print_checked('some stdout output'); + if (!is_newer('/output', '/input-1', '/input-2')) { + open(my $fh, '>', '/output'); + print_fh($fh, '/output', 'some stuff'); + close($fh); + } + +=head1 REQUIREMENTS + +Perl 5.24 or later and the List::SomeUtils module, available from CPAN. + +=head1 DESCRIPTION + +This module collects utility functions used by other App::DocKnot modules. It +is not really intended for use outside of DocKnot, but these functions can be +used if desired. + +=head1 FUNCTIONS + +=over 4 + +=item is_newer(FILE, SOURCE[, SOURCE ...]) + +Returns a true value if FILE exists and has a last modified time that is newer +or equal to the last modified times of all SOURCE files, and otherwise returns +a false value. Used primarily to determine if a given output file is +up-to-date with respect to its source files. + +=item print_checked(ARG[, ARG ...]) + +The same as print (without a file handle argument), except that it throws a +text exception on failure as if autodie affected print (which it unfortunately +doesn't because print cannot be prototyped). + +=item print_fh(FH, NAME, DATA[, DATA ...]) + +Writes the concatenation of the DATA elements (interpreted as scalar strings) +to the file handle FH. NAME should be the name of the file open as FH, and is +used for error reporting. + +This is mostly equivalent to C<print {fh}> but throws a text exception in the +event of a failure. + +=back + +=head1 AUTHOR + +Russ Allbery <rra@cpan.org> + +=head1 COPYRIGHT AND LICENSE + +Copyright 1999-2011, 2013, 2021 Russ Allbery <rra@cpan.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +=head1 SEE ALSO + +L<App::DocKnot> + +This module is part of the App-DocKnot distribution. The current version of +DocKnot is available from CPAN, or directly from its web site at +L<https://www.eyrie.org/~eagle/software/docknot/>. + +=cut + +# Local Variables: +# copyright-at-end-flag: t +# End: |