forked from HPR/hpr-tools
		
	FAQ/FAQ.mkd, FAQ/Makefile: this version of the FAQ is now out of date
    and probably should be deleted.
InternetArchive/repair_item: script to upload missing shows after tie
    out errors during the normal upload; still under development.
InternetArchive/update_state: script to update show state in the
    'reservations' table in the database. Uses the CMS interface.
Link_Checker/scan_links: under development. Not currently usable.
Miscellaneous/fix_tags: audio metadata manipulation script. Recently
    added to this repo for convenience. Updates for 'experimental::try',
    the official Perl try/catch.
PostgreSQL_Database/add_hosts_to_show, PostgreSQL_Database/hpr_schema_2.pgsql,
    PostgreSQL_Database/nuke_n_pave.sh: an old experimental Pg database
    to take over from the previous MySQL version (from before 2023).
    Kept for reference; never implemented.
		
	
		
			
				
	
	
		
			619 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			619 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env perl
 | |
| #===============================================================================
 | |
| #
 | |
| #         FILE: scan_links
 | |
| #
 | |
| #        USAGE: ./scan_links [-help] [-[no]verbose] [-config=FILE]
 | |
| #
 | |
| #  DESCRIPTION: Scan the notes in the database for links. Test each link to
 | |
| #               see if it's available. Keep a record of the date, show, link
 | |
| #               and result. If a link fails more than N tests take action.
 | |
| #               Possible actions are:
 | |
| #               - report the problem
 | |
| #               - look for the link on archive.org
 | |
| #               - modify the notes
 | |
| #
 | |
| #      OPTIONS: ---
 | |
| # REQUIREMENTS: ---
 | |
| #         BUGS: ---
 | |
| #        NOTES: ---
 | |
| #       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
 | |
| #      VERSION: 0.0.2
 | |
| #      CREATED: 2017-04-02 14:09:02
 | |
| #     REVISION: 2022-06-02 23:18:13
 | |
| #
 | |
| #===============================================================================
 | |
| 
 | |
| use 5.010;
 | |
| use strict;
 | |
| use warnings;
 | |
| use utf8;
 | |
| use experimental 'smartmatch';
 | |
| 
 | |
| use Carp;
 | |
| use Getopt::Long;
 | |
| use Pod::Usage;
 | |
| 
 | |
| use Config::General;
 | |
| use File::Slurper qw{ read_text read_lines };
 | |
| use Try::Tiny;
 | |
| 
 | |
| use HTML::TreeBuilder 5 -weak;
 | |
| use HTML::Entities;
 | |
| use List::Util qw{ min max };
 | |
| use List::MoreUtils qw{ any };
 | |
| use LWP::Simple;
 | |
| 
 | |
| use DBI;
 | |
| use SQL::Abstract;
 | |
| use SQL::Abstract::Plugin::InsertMulti;
 | |
| 
 | |
| use Data::Dumper;
 | |
| 
 | |
| #
 | |
| # Version number (manually incremented)
 | |
| #
 | |
| our $VERSION = '0.0.2';
 | |
| 
 | |
| #
 | |
| # Script and directory names
 | |
| #
 | |
| ( my $PROG = $0 ) =~ s|.*/||mx;
 | |
| ( my $DIR  = $0 ) =~ s|/?[^/]*$||mx;
 | |
| $DIR = '.' unless $DIR;
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Declarations
 | |
| #-------------------------------------------------------------------------------
 | |
| #
 | |
| # Constants and other declarations
 | |
| #
 | |
| my $basedir       = "$ENV{HOME}/HPR/Link_Checker";
 | |
| my $configfile    = "$basedir/.$PROG.cfg";
 | |
| my $db1configfile = "$basedir/.hpr_db.cfg";
 | |
| my $database2     = "$basedir/ia.db";
 | |
| 
 | |
| my ( $dbh1, $dbh2, $sql1, $sth1, $h1 );
 | |
| 
 | |
| #
 | |
| # Enable Unicode mode
 | |
| #
 | |
| binmode STDOUT, ":encoding(UTF-8)";
 | |
| binmode STDERR, ":encoding(UTF-8)";
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Options and arguments
 | |
| #-------------------------------------------------------------------------------
 | |
| #
 | |
| # Option defaults
 | |
| #
 | |
| my $DEFDEBUG = 0;
 | |
| my $DEFFROM = 1;
 | |
| my $DEFCOUNT = 10;
 | |
| 
 | |
| #
 | |
| # Process options
 | |
| #
 | |
| my %options;
 | |
| Options( \%options );
 | |
| 
 | |
| #
 | |
| # Default help is minimal
 | |
| #
 | |
| pod2usage( -msg => "$PROG version $VERSION\n", -exitval => 1 )
 | |
|     if ( $options{'help'} );
 | |
| 
 | |
| #
 | |
| # The -documentation or -man option shows the full POD documentation through
 | |
| # a pager for convenience
 | |
| #
 | |
| pod2usage( -msg => "$PROG version $VERSION\n", -exitval => 1, -verbose => 2 )
 | |
|     if ( $options{'documentation'} );
 | |
| 
 | |
| #
 | |
| # Collect options
 | |
| #
 | |
| my $DEBUG = ( defined( $options{debug} ) ? $options{debug} : $DEFDEBUG );
 | |
| 
 | |
| my $db1cfgfile
 | |
|     = ( defined( $options{dbconfig} ) ? $options{dbconfig} : $db1configfile );
 | |
| 
 | |
| my $cfgfile
 | |
|     = ( defined( $options{config} ) ? $options{config} : $configfile );
 | |
| 
 | |
| my $dry_run = ( defined( $options{'dry-run'} ) ? $options{'dry-run'} : 0 );
 | |
| my $verbose = ( defined( $options{verbose} )   ? $options{verbose}   : 0 );
 | |
| my $from    = ( defined( $options{from} )  ? $options{from}  : $DEFFROM );
 | |
| my $count   = ( defined( $options{count} ) ? $options{count} : $DEFCOUNT );
 | |
| 
 | |
| $from = $DEFFROM if $from < 1;
 | |
| my @episodes = ( ( $from .. $from + $count ) );
 | |
| 
 | |
| #
 | |
| # Report on the options in debug mode
 | |
| #
 | |
| if ($DEBUG > 1) {
 | |
|     _debug(1,'$DEBUG      = ' . $DEBUG);
 | |
|     _debug(1,'$dry-run    = ' . $dry_run);
 | |
|     _debug(1,'$verbose    = ' . $verbose);
 | |
|     _debug(1,'$db1cfgfile = ' . $db1cfgfile);
 | |
|     _debug(1,'$cfgfile    = ' . $cfgfile);
 | |
|     _debug(1,'$from       = ' . $from);
 | |
|     _debug(1,'$count      = ' . $count);
 | |
|     _debug(1,'$#episodes  = ' . $#episodes);
 | |
| }
 | |
| 
 | |
| #
 | |
| # Sanity checks
 | |
| #
 | |
| die "Unable to find $cfgfile\n" unless ( -e $cfgfile );
 | |
| die "Unable to find $db1cfgfile\n" unless ( -e $db1cfgfile );
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Configuration file - load data
 | |
| #-------------------------------------------------------------------------------
 | |
| my $conf = Config::General->new(
 | |
|     -ConfigFile      => $cfgfile,
 | |
|     -InterPolateVars => 1,
 | |
|     -ExtendedAccess  => 1,
 | |
| );
 | |
| my %config = $conf->getall();
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Connect to the database
 | |
| #-------------------------------------------------------------------------------
 | |
| my $db1conf = Config::General->new(
 | |
|     -ConfigFile      => $db1cfgfile,
 | |
|     -InterPolateVars => 1,
 | |
|     -ExtendedAccess  => 1,
 | |
| );
 | |
| my %db1cfg = $db1conf->getall();
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Database configuration file - load data
 | |
| #-------------------------------------------------------------------------------
 | |
| my $dbhost = $db1cfg{database}->{host} // '127.0.0.1';
 | |
| my $dbport = $db1cfg{database}->{port} // 3306;
 | |
| my $dbname = $db1cfg{database}->{name};
 | |
| my $dbuser = $db1cfg{database}->{user};
 | |
| my $dbpwd  = $db1cfg{database}->{password};
 | |
| $dbh1 = DBI->connect( "dbi:mysql:host=$dbhost;port=$dbport;database=$dbname",
 | |
|     $dbuser, $dbpwd, { AutoCommit => 1, RaiseError => 1 } )
 | |
|     or die $DBI::errstr;
 | |
| 
 | |
| #
 | |
| # Enable client-side UTF8
 | |
| #
 | |
| $dbh1->{mysql_enable_utf8} = 1;
 | |
| 
 | |
| #
 | |
| # Set the local timezone to UTC for this connection
 | |
| #
 | |
| $dbh1->do("set time_zone = '+00:00'") or carp $dbh1->errstr;
 | |
| 
 | |
| emit( $verbose >= 2, "Opened MySQL database\n" );
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # Connect to the SQLite database
 | |
| #-------------------------------------------------------------------------------
 | |
| $dbh2 = DBI->connect( "dbi:SQLite:dbname=$database2", "", "" )
 | |
|     or die $DBI::errstr;
 | |
| 
 | |
| $dbh2->do("PRAGMA foreign_keys = ON") or die $DBI::errstr;
 | |
| 
 | |
| emit( $verbose >= 2, "Opened SQLite database\n" );
 | |
| 
 | |
| #
 | |
| # The main MySQL query
 | |
| #
 | |
| $sql1 = q{
 | |
|     SELECT * FROM eps WHERE id BETWEEN ? AND ?
 | |
| };
 | |
| 
 | |
| $sth1 = $dbh1->prepare($sql1);
 | |
| 
 | |
| $sth1->execute( $from, $from + $count );
 | |
| if ( $dbh1->err ) {
 | |
|     die $dbh1->errstr;
 | |
| }
 | |
| 
 | |
| while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|     printf "%04d %s %s\n", $h1->{id},$h1->{date},$h1->{title};
 | |
| }
 | |
| 
 | |
| exit;
 | |
| 
 | |
| #emit( $verbose >= 2, "Configuration file: ", $cfgfile, "\n" );
 | |
| #emit( $verbose >= 2, "DB configuration file: ", $db1cfgfile, "\n" );
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: find_external_links
 | |
| #      PURPOSE: Parses the HTML in a string for links so that attached assets
 | |
| #               on the HPR site can also be parsed and to collect external
 | |
| #               links for testing.
 | |
| #   PARAMETERS: $episode        episode number we're dealing with
 | |
| #               $html           string containing HTML
 | |
| #               $rlinks         hashref to receive the links found
 | |
| #      RETURNS: Number of links found
 | |
| #  DESCRIPTION: Given HTML from the main notes or a subsidiary file the
 | |
| #               function parses this looking for links in 'a' or 'img' tags.
 | |
| #               Links are standardised, making them absolute if relative and
 | |
| #               removing any 'fragment'. The links need to be to HTML files on
 | |
| #               the HPR website to be of use in recursing to subsidiary
 | |
| #               levels. Otherwise they need to be external links that we will
 | |
| #               test.
 | |
| #               Having found a local link the filename part is extracted. If
 | |
| #               it follows the format 'hpr9999' then it's checked to see if
 | |
| #               it's for the current show. If not it's ignored. If the
 | |
| #               filename ends with a '/' then it's assumed it's shorthand for
 | |
| #               'index.html' so this name is appended. If the local filename
 | |
| #               ends with '.html' then we need to parse it in turn, so we get
 | |
| #               the contents of the link and recurse to parse it.
 | |
| #               Then, if external, the link and filename are stashed in the
 | |
| #               hash referenced by $rlinks. We return the number of external
 | |
| #               links found in the pass through the HTML.
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: Based on 'find_links' in 'upload_manager'.
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub find_external_links {
 | |
|     my ( $episode, $html, $rlinks ) = @_;
 | |
| 
 | |
|     my ($tree, $epstr, $linkre,    $re2, $filepath,
 | |
|         $uri,  $slink, $linkcount, $content
 | |
|     );
 | |
| 
 | |
|     _debug( $DEBUG >= 3, "find_external_links enter" );
 | |
| 
 | |
|     #
 | |
|     # Create a tree object
 | |
|     #
 | |
|     $tree = HTML::TreeBuilder->new;
 | |
|     $tree->ignore_unknown(0);
 | |
|     $tree->no_expand_entities(1);
 | |
|     $tree->p_strict(1);
 | |
|     $tree->store_comments(1);
 | |
|     $tree->warn(1);
 | |
| 
 | |
|     $tree->parse_content($html)
 | |
|         or die "HTML::TreeBuilder failed to parse notes: $!\n";
 | |
| 
 | |
|     my $baseURL = "http://hackerpublicradio.org";
 | |
| 
 | |
|     $epstr = sprintf( "hpr%04d", $episode );
 | |
|     $linkre = qr{
 | |
|         ^https?://
 | |
|         (?:www.)?
 | |
|         (?:hacker|hobby)publicradio.org/
 | |
|         (.+)$
 | |
|     }x;
 | |
| 
 | |
|     #
 | |
|     # Counting new links found and stashed
 | |
|     #
 | |
|     $linkcount = 0;
 | |
| 
 | |
|     #
 | |
|     # Scan for links
 | |
|     #
 | |
|     for ( @{ $tree->extract_links( 'a', 'img' ) } ) {
 | |
|         my ( $link, $element, $attr, $tag ) = @$_;
 | |
| 
 | |
|         #
 | |
|         # Standardise the link (expands relative URLs, removes any fragment).
 | |
|         # Set $URI::ABS_REMOTE_LEADING_DOTS to ensure leading dots in relative
 | |
|         # URIs are removed.
 | |
|         #
 | |
|         local $URI::ABS_REMOTE_LEADING_DOTS = 1;
 | |
|         $uri = URI->new_abs( $link, $baseURL );
 | |
|         $slink = sprintf( "%s:%s", $uri->scheme, $uri->opaque );
 | |
| 
 | |
|         #
 | |
|         # Is it an HPR link?
 | |
|         #
 | |
|         if ( $slink =~ $linkre ) {
 | |
|             #
 | |
|             # The URL we found might be a link into an HTML file with an
 | |
|             # '#anchor' component ("fragment"). Save the last bracketed match,
 | |
|             # without any 'fragment' if there is one to get a clean filename
 | |
|             # or path.
 | |
|             #
 | |
|             ( $filepath = "$+" ) =~ s/#.*$//;
 | |
| 
 | |
|             _debug( $DEBUG >= 3, "Link:      $slink\n" );
 | |
|             _debug( $DEBUG >= 3, "File path: $filepath\n" );
 | |
| 
 | |
|             #
 | |
|             # Does this file path begin with an 'hpr' prefix? If so is it the
 | |
|             # show id? If not we don't want to process it.
 | |
|             #
 | |
|             if ( $filepath =~ /^(hpr[0-9]{1,4})/ ) {
 | |
|                 if ( $1 ne $epstr ) {
 | |
|                     _debug( $DEBUG >= 3, "Ignored $slink\n" );
 | |
|                     next;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             #
 | |
|             # The path and URL might end with a slash which means the URL is
 | |
|             # relying on the Web server to fill in the filename as
 | |
|             # 'index.html'. We have to make this explicit.
 | |
|             #
 | |
|             if ( $slink =~ /\/$/ ) {
 | |
|                 $slink    .= 'index.html';
 | |
|                 $filepath .= 'index.html';
 | |
|             }
 | |
| 
 | |
|             #
 | |
|             # Initialise this hash element if needed
 | |
|             #
 | |
|             unless ( exists( $rlinks->{$episode} ) ) {
 | |
|                 $rlinks->{$episode} = [];
 | |
|             }
 | |
| 
 | |
|             #
 | |
|             # Stash this filename if it's not already stashed, and if it's
 | |
|             # HTML get the link and recurse
 | |
|             #
 | |
|             unless (
 | |
|                 any { $_->{filename} eq $filepath }
 | |
|                 @{ $rlinks->{$episode} }
 | |
|                 )
 | |
|             {
 | |
|                 _debug( $DEBUG >= 3, "Stashed $slink and $filepath\n" );
 | |
| 
 | |
|                 push(
 | |
|                     @{ $rlinks->{$episode} },
 | |
|                     { filename => $filepath, URL => $slink }
 | |
|                 );
 | |
|                 $linkcount++;
 | |
| 
 | |
|                 #
 | |
|                 # An HTML file has to be investigated
 | |
|                 #
 | |
|                 if ( $filepath =~ /\.html$/ ) {
 | |
|                     $content = get($slink);
 | |
|                     unless ( defined($content) ) {
 | |
|                         carp "Link $slink returned nothing\n";
 | |
|                     }
 | |
|                     else {
 | |
|                         $linkcount
 | |
|                             += find_links( $episode, $content, $rlinks );
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         #
 | |
|         # It's not an HPR URL
 | |
|         #
 | |
|         else {
 | |
|             #
 | |
|             # Stash this filename if it's not already stashed
 | |
|             #
 | |
|             unless (
 | |
|                 any { $_->{filename} eq $filepath }
 | |
|                 @{ $rlinks->{$episode} }
 | |
|                 )
 | |
|             {
 | |
|                 _debug( $DEBUG >= 3, "Stashed $slink and $filepath\n" );
 | |
| 
 | |
|                 push(
 | |
|                     @{ $rlinks->{$episode} },
 | |
|                     { filename => $filepath, URL => $slink }
 | |
|                 );
 | |
|                 $linkcount++;
 | |
| 
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _debug( $DEBUG >= 3, "find_links exiting with $linkcount links\n" );
 | |
| 
 | |
|     #
 | |
|     # Return the link count
 | |
|     #
 | |
|     return $linkcount;
 | |
| 
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: emit
 | |
| #      PURPOSE: Print text on STDERR unless silent mode has been selected
 | |
| #   PARAMETERS: - Boolean indicating whether to print or not
 | |
| #               - list of arguments to 'print'
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION: This is a wrapper around 'print' to determine whether to send
 | |
| #               a message to STDERR depending on a boolean. We need this to be
 | |
| #               able to make the script silent when the -verbose option is
 | |
| #               not selected
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub emit {
 | |
|     if (shift) {
 | |
|         print STDERR @_;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: _debug
 | |
| #      PURPOSE: Prints debug reports
 | |
| #   PARAMETERS: $active         Boolean: 1 for print, 0 for no print
 | |
| #               $message        Message to print
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION: Outputs a message if $active is true. It removes any trailing
 | |
| #               newline and then adds one in the 'print' to the caller doesn't
 | |
| #               have to bother. Prepends the message with 'D> ' to show it's
 | |
| #               a debug message.
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub _debug {
 | |
|     my ( $active, $message ) = @_;
 | |
| 
 | |
|     chomp($message);
 | |
|     print STDERR "D> $message\n" if $active;
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: Options
 | |
| #      PURPOSE: Processes command-line options
 | |
| #   PARAMETERS: $optref     Hash reference to hold the options
 | |
| #      RETURNS: Undef
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: no exceptions
 | |
| #     COMMENTS: none
 | |
| #     SEE ALSO: n/a
 | |
| #===============================================================================
 | |
| sub Options {
 | |
|     my ($optref) = @_;
 | |
| 
 | |
|     my @options = (
 | |
|         "help",     "documentation|man", "debug=i", "dry-run!",
 | |
|         "verbose+", "dbconfig=s",        "from=s",  "count=i",
 | |
|     );
 | |
| 
 | |
|     if ( !GetOptions( $optref, @options ) ) {
 | |
|         pod2usage( -msg => "$PROG version $VERSION\n", -exitval => 1,
 | |
|             -verbose => 0 );
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| __END__
 | |
| 
 | |
| #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 | |
| #  Application Documentation
 | |
| #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 | |
| #{{{
 | |
| 
 | |
| =head1 NAME
 | |
| 
 | |
| <application name> - <One line description of application's purpose>
 | |
| 
 | |
| =head1 VERSION
 | |
| 
 | |
| The initial template usually just has:
 | |
| 
 | |
| This documentation refers to <application name> version 0.0.2
 | |
| 
 | |
| 
 | |
| =head1 USAGE
 | |
| 
 | |
|     # Brief working invocation example(s) here showing the most common usage(s)
 | |
| 
 | |
|     # This section will be as far as many users ever read
 | |
|     # so make it as educational and exemplary as possible.
 | |
| 
 | |
| 
 | |
| =head1 REQUIRED ARGUMENTS
 | |
| 
 | |
| A complete list of every argument that must appear on the command line.
 | |
| when the application  is invoked, explaining what each of them does, any
 | |
| restrictions on where each one may appear (i.e. flags that must appear
 | |
| before or after filenames), and how the various arguments and options
 | |
| may interact (e.g. mutual exclusions, required combinations, etc.)
 | |
| 
 | |
| If all of the application's arguments are optional this section
 | |
| may be omitted entirely.
 | |
| 
 | |
| 
 | |
| =head1 OPTIONS
 | |
| 
 | |
| A complete list of every available option with which the application
 | |
| can be invoked, explaining what each does, and listing any restrictions,
 | |
| or interactions.
 | |
| 
 | |
| If the application has no options this section may be omitted entirely.
 | |
| 
 | |
| 
 | |
| =head1 DESCRIPTION
 | |
| 
 | |
| A full description of the application and its features.
 | |
| May include numerous subsections (i.e. =head2, =head3, etc.)
 | |
| 
 | |
| 
 | |
| =head1 DIAGNOSTICS
 | |
| 
 | |
| A list of every error and warning message that the application can generate
 | |
| (even the ones that will "never happen"), with a full explanation of each
 | |
| problem, one or more likely causes, and any suggested remedies. If the
 | |
| application generates exit status codes (e.g. under Unix) then list the exit
 | |
| status associated with each error.
 | |
| 
 | |
| 
 | |
| =head1 CONFIGURATION AND ENVIRONMENT
 | |
| 
 | |
| A full explanation of any configuration system(s) used by the application,
 | |
| including the names and locations of any configuration files, and the
 | |
| meaning of any environment variables or properties that can be set. These
 | |
| descriptions must also include details of any configuration language used
 | |
| 
 | |
| 
 | |
| =head1 DEPENDENCIES
 | |
| 
 | |
| A list of all the other modules that this module relies upon, including any
 | |
| restrictions on versions, and an indication whether these required modules are
 | |
| part of the standard Perl distribution, part of the module's distribution,
 | |
| or must be installed separately.
 | |
| 
 | |
| 
 | |
| =head1 INCOMPATIBILITIES
 | |
| 
 | |
| A list of any modules that this module cannot be used in conjunction with.
 | |
| This may be due to name conflicts in the interface, or competition for
 | |
| system or program resources, or due to internal limitations of Perl
 | |
| (for example, many modules that use source code filters are mutually
 | |
| incompatible).
 | |
| 
 | |
| 
 | |
| =head1 BUGS AND LIMITATIONS
 | |
| 
 | |
| A list of known problems with the module, together with some indication
 | |
| whether they are likely to be fixed in an upcoming release.
 | |
| 
 | |
| Also a list of restrictions on the features the module does provide:
 | |
| data types that cannot be handled, performance issues and the circumstances
 | |
| in which they may arise, practical limitations on the size of data sets,
 | |
| special cases that are not (yet) handled, etc.
 | |
| 
 | |
| The initial template usually just has:
 | |
| 
 | |
| There are no known bugs in this module.
 | |
| Please report problems to <Maintainer name(s)>  (<contact address>)
 | |
| Patches are welcome.
 | |
| 
 | |
| =head1 AUTHOR
 | |
| 
 | |
| <Author name(s)>  (<contact address>)
 | |
| 
 | |
| 
 | |
| =head1 LICENCE AND COPYRIGHT
 | |
| 
 | |
| Copyright (c) <year> <copyright holder> (<contact address>). All rights reserved.
 | |
| 
 | |
| Followed by whatever licence you wish to release it under.
 | |
| For Perl code that is often just:
 | |
| 
 | |
| This module is free software; you can redistribute it and/or
 | |
| modify it under the same terms as Perl itself. See perldoc perlartistic.
 | |
| 
 | |
| This program is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #}}}
 | |
| 
 | |
| # [zo to open fold, zc to close or za to toggle]
 | |
| 
 | |
| # vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
 | |
| 
 |