forked from HPR/hpr-tools
		
	Updates from previous repo
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.
			
			
This commit is contained in:
		| @@ -3,7 +3,7 @@ | ||||
| # | ||||
| #         FILE: scan_links | ||||
| # | ||||
| #        USAGE: ./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 | ||||
| @@ -18,9 +18,9 @@ | ||||
| #         BUGS: --- | ||||
| #        NOTES: --- | ||||
| #       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com | ||||
| #      VERSION: 0.0.1 | ||||
| #      VERSION: 0.0.2 | ||||
| #      CREATED: 2017-04-02 14:09:02 | ||||
| #     REVISION: 2017-04-02 14:51:19 | ||||
| #     REVISION: 2022-06-02 23:18:13 | ||||
| # | ||||
| #=============================================================================== | ||||
|  | ||||
| @@ -28,12 +28,32 @@ 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.1'; | ||||
| our $VERSION = '0.0.2'; | ||||
|  | ||||
| # | ||||
| # Script and directory names | ||||
| @@ -48,8 +68,12 @@ $DIR = '.' unless $DIR; | ||||
| # | ||||
| # Constants and other declarations | ||||
| # | ||||
| my $basedir    = "$ENV{HOME}"; | ||||
| 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 | ||||
| @@ -57,8 +81,538 @@ my $basedir    = "$ENV{HOME}"; | ||||
| 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 | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user