summaryrefslogtreecommitdiff
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
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.
-rw-r--r--Changes3
-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
6 files changed, 229 insertions, 167 deletions
diff --git a/Changes b/Changes
index 9d14a27..4de1bc4 100644
--- a/Changes
+++ b/Changes
@@ -12,6 +12,9 @@
master. The first of main or master that's found in the repository
will be used.
+ - Move some utility functions into a new App::DocKnot::Util module. This
+ is primarily intended for internal use by other App::DocKnot modules.
+
- Fix unintended localization of dates in RSS output, which are supposed
to be RFC 2822 dates and therefore always use English month and day of
week names. Thanks to Slaven Rezić for testing.
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: