summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRuss Allbery <rra@cpan.org>2021-12-23 21:57:43 -0800
committerRuss Allbery <rra@cpan.org>2021-12-23 21:57:43 -0800
commit74c5d402923e0d4085200a38cfddeea8af16f341 (patch)
tree3fa4aa45d6510ce64f6c9f0443c3f4a6779e2b4e /lib
parent75c60cbc6a3c81f1991fac42966e13fa3cc77b51 (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.pm88
-rw-r--r--lib/App/DocKnot/Spin/Pointer.pm43
-rw-r--r--lib/App/DocKnot/Spin/RSS.pm65
-rw-r--r--lib/App/DocKnot/Spin/Thread.pm25
-rw-r--r--lib/App/DocKnot/Util.pm172
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: