forked from HPR/hpr-tools
		
	
		
			
	
	
		
			667 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			667 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
								 | 
							
								#!/usr/bin/env perl
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#         FILE: edit_series
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#        USAGE: ./edit_series [-help] [-debug=N] [-[no]update] [-config=FILE]
							 | 
						||
| 
								 | 
							
								#               [-series_id=id] [-[no]regex] [-[no]description] [-[no]private]
							 | 
						||
| 
								 | 
							
								#               [-[no]valid] series_name
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: A simple editor for the HPR miniseries table
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#      OPTIONS: ---
							 | 
						||
| 
								 | 
							
								# REQUIREMENTS: ---
							 | 
						||
| 
								 | 
							
								#         BUGS: ---
							 | 
						||
| 
								 | 
							
								#        NOTES: Based on edit_host
							 | 
						||
| 
								 | 
							
								#       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
							 | 
						||
| 
								 | 
							
								#      VERSION: 0.0.3
							 | 
						||
| 
								 | 
							
								#      CREATED: 2020-06-21 17:58:19
							 | 
						||
| 
								 | 
							
								#     REVISION: 2021-06-23 22:11:13
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use 5.010;
							 | 
						||
| 
								 | 
							
								use strict;
							 | 
						||
| 
								 | 
							
								use warnings;
							 | 
						||
| 
								 | 
							
								use utf8;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Getopt::Long;
							 | 
						||
| 
								 | 
							
								use Pod::Usage;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Config::General;
							 | 
						||
| 
								 | 
							
								use File::Temp;
							 | 
						||
| 
								 | 
							
								use File::Slurper qw{ read_text };
							 | 
						||
| 
								 | 
							
								use SQL::Abstract;
							 | 
						||
| 
								 | 
							
								use DBI;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								use Data::Dumper;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Version number (manually incremented)
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								our $VERSION = '0.0.3';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# 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/Database";
							 | 
						||
| 
								 | 
							
								my $configfile = "$basedir/.hpr_db.cfg";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Declarations
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my ( $dbh, $sth1, $h1, $rc );
							 | 
						||
| 
								 | 
							
								my ( $series_name, %changes );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Enable Unicode mode
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								binmode STDOUT, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								binmode STDERR, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Options and arguments
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my %options;
							 | 
						||
| 
								 | 
							
								Options( \%options );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Default help
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								pod2usage( -msg => "Version $VERSION\n", -exitval => 1 )
							 | 
						||
| 
								 | 
							
								    if ( $options{'help'} );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Collect options
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $DEBUG = ( $options{'debug'} ? $options{'debug'} : 0 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $cfgfile
							 | 
						||
| 
								 | 
							
								    = ( defined( $options{config} ) ? $options{config} : $configfile );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $updatedb = ( defined( $options{'updatedb'} ) ? $options{'updatedb'} : 0 );
							 | 
						||
| 
								 | 
							
								my $regexp   = ( defined( $options{'regexp'} )   ? $options{'regexp'}   : 0 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								my $description = $options{'description'};
							 | 
						||
| 
								 | 
							
								my $private     = $options{'private'};
							 | 
						||
| 
								 | 
							
								my $valid       = $options{'valid'};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# There must be at least one field to change. We check for the definition here
							 | 
						||
| 
								 | 
							
								# because the values returned may be zero or one or the variable may be
							 | 
						||
| 
								 | 
							
								# undefined.
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								die "Select one of -[no]description, -[no]private and -[no]valid\n"
							 | 
						||
| 
								 | 
							
								    unless ( defined($description) || defined($private) || defined($valid) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Was a series id provided (through an option)?
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $series_id = $options{'series_id'};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Deal with the two routes: one via the unique series id, and the other vai the
							 | 
						||
| 
								 | 
							
								# less unique series name
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								unless ($series_id) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Get the arg
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $series_name = shift;
							 | 
						||
| 
								 | 
							
								    pod2usage( -msg => "Specify the series name\n", -exitval => 1 )
							 | 
						||
| 
								 | 
							
								        unless $series_name;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Sanity check
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								die "Unable to find $cfgfile\n" unless ( -e $cfgfile );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Load configuration data
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $conf = new Config::General(
							 | 
						||
| 
								 | 
							
								    -ConfigFile      => $cfgfile,
							 | 
						||
| 
								 | 
							
								    -InterPolateVars => 1,
							 | 
						||
| 
								 | 
							
								    -ExtendedAccess  => 1
							 | 
						||
| 
								 | 
							
								);
							 | 
						||
| 
								 | 
							
								my %config = $conf->getall();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Connect to the database
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								my $dbhost = $config{database}->{host} // '127.0.0.1';
							 | 
						||
| 
								 | 
							
								my $dbport = $config{database}->{port} // 3306;
							 | 
						||
| 
								 | 
							
								my $dbname = $config{database}->{name};
							 | 
						||
| 
								 | 
							
								my $dbuser = $config{database}->{user};
							 | 
						||
| 
								 | 
							
								my $dbpwd  = $config{database}->{password};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# 2022-04-12 The MariaDB driver was there one minute and then it wasn't!
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								#$dbh = DBI->connect( "DBI:MariaDB:database=$dbname;host=$dbhost;port=$dbport",
							 | 
						||
| 
								 | 
							
								#    $dbuser, $dbpwd, { AutoCommit => 1 } )
							 | 
						||
| 
								 | 
							
								#    or die $DBI::errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$dbh = DBI->connect( "dbi:mysql:database=$dbname;host=$dbhost;port=$dbport",
							 | 
						||
| 
								 | 
							
								    $dbuser, $dbpwd, { AutoCommit => 1 } )
							 | 
						||
| 
								 | 
							
								    or die $DBI::errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Enable client-side UTF8
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								$dbh->{mysql_enable_utf8} = 1;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Prepare to read the database either for the series_id or the exact or
							 | 
						||
| 
								 | 
							
								# approximate name
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ($series_id) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Simple series_id query
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $sth1 = $dbh->prepare(q{SELECT * FROM miniseries WHERE id = ?});
							 | 
						||
| 
								 | 
							
								    $sth1->execute($series_id);
							 | 
						||
| 
								 | 
							
								    if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								        warn $dbh->errstr;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Series name query
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if ($regexp) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Regexp match requested. Count how many matches there are
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        $sth1 = $dbh->prepare(
							 | 
						||
| 
								 | 
							
								            q{SELECT count(*) AS count FROM miniseries WHERE name REGEXP ?});
							 | 
						||
| 
								 | 
							
								        $sth1->execute($series_name);
							 | 
						||
| 
								 | 
							
								        if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								            warn $dbh->errstr;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if ( $h1 = $sth1->fetchrow_hashref ) {
							 | 
						||
| 
								 | 
							
								            my $matches = $h1->{count};
							 | 
						||
| 
								 | 
							
								            if ($matches > 1) {
							 | 
						||
| 
								 | 
							
								                die "Too many matches to regex '$series_name' ($matches)\n";
							 | 
						||
| 
								 | 
							
								            } elsif ($matches == 0) {
							 | 
						||
| 
								 | 
							
								                die "No matches to regex '$series_name'\n";
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            die "Unable to find series matching regex '$series_name'\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $sth1 = $dbh->prepare(q{SELECT * FROM miniseries WHERE name REGEXP ?});
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        $sth1 = $dbh->prepare(q{SELECT * FROM miniseries WHERE name = ?});
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Execute the query
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $sth1->execute($series_name);
							 | 
						||
| 
								 | 
							
								    if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								        warn $dbh->errstr;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								# Did we find it?
							 | 
						||
| 
								 | 
							
								#
							 | 
						||
| 
								 | 
							
								if ( $h1 = $sth1->fetchrow_hashref ) {
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Found. Save the series_id to simplify the update if we don't already have
							 | 
						||
| 
								 | 
							
								    # it
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    $series_id //= $h1->{id};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Report on the series details
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    printf "Series details\n" .
							 | 
						||
| 
								 | 
							
								        "Id:          %s\n" .
							 | 
						||
| 
								 | 
							
								        "Name:        %s\n" .
							 | 
						||
| 
								 | 
							
								        "Description: %s\n" .
							 | 
						||
| 
								 | 
							
								        "Private:     %s\n" .
							 | 
						||
| 
								 | 
							
								        "Image:       '%s'\n" .
							 | 
						||
| 
								 | 
							
								        "Valid:       %s\n",
							 | 
						||
| 
								 | 
							
								        $h1->{id},
							 | 
						||
| 
								 | 
							
								        $h1->{name},
							 | 
						||
| 
								 | 
							
								        (   length( $h1->{description} ) > 80
							 | 
						||
| 
								 | 
							
								            ? substr( $h1->{description}, 0, 80 ) . '...'
							 | 
						||
| 
								 | 
							
								            : $h1->{description} ),
							 | 
						||
| 
								 | 
							
								        $h1->{private},
							 | 
						||
| 
								 | 
							
								        $h1->{image},
							 | 
						||
| 
								 | 
							
								        $h1->{valid};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # So what needs changing?
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    #<<< [perltidy messes up the following]
							 | 
						||
| 
								 | 
							
								    if ($description) {
							 | 
						||
| 
								 | 
							
								        $changes{description} = check_field( 'description',
							 | 
						||
| 
								 | 
							
								            scalar( run_editor( $h1->{description}, ['+set paste'] ) ), 1500, qr{(\n)} );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (defined($private)) {
							 | 
						||
| 
								 | 
							
								        $changes{private} = $private if ($h1->{private} ne $private);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (defined($valid)) {
							 | 
						||
| 
								 | 
							
								        $changes{valid} = $valid if ($h1->{valid} ne $valid);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    #>>>
							 | 
						||
| 
								 | 
							
								    print Dumper( \%changes ), "\n" if ( $DEBUG > 2 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Are we updating the database then?
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    if ($updatedb) {
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # Was anything edited?
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if (%changes) {
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            # Did the edits do anything? If not delete the element from the
							 | 
						||
| 
								 | 
							
								            # hash since there's no point writing it to the database
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            for my $key ( keys(%changes) ) {
							 | 
						||
| 
								 | 
							
								                if ( $changes{$key} eq $h1->{$key} ) {
							 | 
						||
| 
								 | 
							
								                    print "No change made to $key, ignored\n";
							 | 
						||
| 
								 | 
							
								                    delete( $changes{$key} );
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            print Dumper( \%changes ), "\n" if ( $DEBUG > 2 );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # If there's anything left apply the changes
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        if (%changes) {
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            # Go into transaction mode here so we can fail safely
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            $rc = $dbh->begin_work or die $dbh->errstr;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            my $sql   = SQL::Abstract->new;
							 | 
						||
| 
								 | 
							
								            my %where = ( id => $series_id );
							 | 
						||
| 
								 | 
							
								            my ( $stmt, @bind )
							 | 
						||
| 
								 | 
							
								                = $sql->update( 'miniseries', \%changes, \%where );
							 | 
						||
| 
								 | 
							
								            #print "$stmt\n";
							 | 
						||
| 
								 | 
							
								            #print join( ",", map {"'$_'"} @bind ), "\n";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            my $sth = $dbh->prepare($stmt);
							 | 
						||
| 
								 | 
							
								            my $rv  = $sth->execute(@bind);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            # Respond to any error by rolling back
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            if ( $dbh->err ) {
							 | 
						||
| 
								 | 
							
								                warn $dbh->errstr;
							 | 
						||
| 
								 | 
							
								                eval { $dbh->rollback };
							 | 
						||
| 
								 | 
							
								                $rv = 0;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            else {
							 | 
						||
| 
								 | 
							
								                $dbh->commit;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            $rv = 0 if ( $rv eq '0E0' );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            # Update the log file
							 | 
						||
| 
								 | 
							
								            #
							 | 
						||
| 
								 | 
							
								            if ($rv) {
							 | 
						||
| 
								 | 
							
								                print "Updated database\n";
							 | 
						||
| 
								 | 
							
								                print "Changed fields: ",
							 | 
						||
| 
								 | 
							
								                    join( ", ", sort( keys(%changes) ) ), "\n";
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            else {
							 | 
						||
| 
								 | 
							
								                print "Series not updated due to error\n";
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        else {
							 | 
						||
| 
								 | 
							
								            print "There was nothing to do\n";
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        print "Database not updated\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								else {
							 | 
						||
| 
								 | 
							
								    if ($series_id) {
							 | 
						||
| 
								 | 
							
								        print "Unable to find series number $series_id\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        print "Unable to find series name $series_name\n";
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exit;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: run_editor
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Run an interactive vim editor on a string
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $string         String to edit
							 | 
						||
| 
								 | 
							
								#               $options        An arrayref containing options for vim
							 | 
						||
| 
								 | 
							
								#                               (optional). Example '+set paste'. Each option
							 | 
						||
| 
								 | 
							
								#                               (such as '-c startinsert') needs to be
							 | 
						||
| 
								 | 
							
								#                               a separate array element.
							 | 
						||
| 
								 | 
							
								#      RETURNS: Edited string
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Makes a temporary file with File::Temp ensuring that the file
							 | 
						||
| 
								 | 
							
								#               is in utf8 mode. Writes the edit string to the file and invokes
							 | 
						||
| 
								 | 
							
								#               the 'vim' editor on it. The resulting file is then read back
							 | 
						||
| 
								 | 
							
								#               into a string and returned to the caller, again taking care to
							 | 
						||
| 
								 | 
							
								#               retain utf8 mode.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: File::Slurp and UTF-8 don't go well together. Moved to
							 | 
						||
| 
								 | 
							
								#               File::Slurper instead
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub run_editor {
							 | 
						||
| 
								 | 
							
								    my ( $string, $options ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Build an arguments array for 'system'
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my @args;
							 | 
						||
| 
								 | 
							
								    push( @args, @$options ) if $options;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Make a temporary file
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    my $tfh = File::Temp->new;
							 | 
						||
| 
								 | 
							
								    binmode $tfh, ":encoding(UTF-8)";
							 | 
						||
| 
								 | 
							
								    my $tfn = $tfh->filename;
							 | 
						||
| 
								 | 
							
								    print $tfh $string if $string;
							 | 
						||
| 
								 | 
							
								    $tfh->close;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    # Add the filename to the arguments
							 | 
						||
| 
								 | 
							
								    #
							 | 
						||
| 
								 | 
							
								    push( @args, $tfn );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    die "Edit failed\n"
							 | 
						||
| 
								 | 
							
								        unless ( system( ( 'vim', @args ) ) == 0 );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return read_text($tfn);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  FUNCTION  ================================================================
							 | 
						||
| 
								 | 
							
								#         NAME: check_field
							 | 
						||
| 
								 | 
							
								#      PURPOSE: Checks the a field is not too long and doesn't contain certain
							 | 
						||
| 
								 | 
							
								#               characters
							 | 
						||
| 
								 | 
							
								#   PARAMETERS: $name           name of field
							 | 
						||
| 
								 | 
							
								#               $field          string to be checked
							 | 
						||
| 
								 | 
							
								#               $maxlen         maximum string length
							 | 
						||
| 
								 | 
							
								#               $regex          regex containing illegal characters to be removed
							 | 
						||
| 
								 | 
							
								#      RETURNS: The input string truncated and with any illegal characters
							 | 
						||
| 
								 | 
							
								#               removed.
							 | 
						||
| 
								 | 
							
								#  DESCRIPTION: Runs a substitution on the string then truncates the result if
							 | 
						||
| 
								 | 
							
								#               it is too long.
							 | 
						||
| 
								 | 
							
								#       THROWS: No exceptions
							 | 
						||
| 
								 | 
							
								#     COMMENTS: None
							 | 
						||
| 
								 | 
							
								#     SEE ALSO: N/A
							 | 
						||
| 
								 | 
							
								#===============================================================================
							 | 
						||
| 
								 | 
							
								sub check_field {
							 | 
						||
| 
								 | 
							
								    my ( $name, $field, $maxlen, $regex ) = @_;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return unless $field;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    $field =~ s/$regex//g;
							 | 
						||
| 
								 | 
							
								    if ( length($field) > $maxlen ) {
							 | 
						||
| 
								 | 
							
								        warn "Field '$name' too long ("
							 | 
						||
| 
								 | 
							
								            . length($field)
							 | 
						||
| 
								 | 
							
								            . "); truncated to "
							 | 
						||
| 
								 | 
							
								            . $maxlen . "\n";
							 | 
						||
| 
								 | 
							
								        $field = substr( $field, 0, $maxlen );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return $field;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#===  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",        "debug=i", "updatedb!",    "config=s",
							 | 
						||
| 
								 | 
							
								        "series_id=i", "regexp",  "description!", "private!",
							 | 
						||
| 
								 | 
							
								        "valid!",
							 | 
						||
| 
								 | 
							
								    );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ( !GetOptions( $optref, @options ) ) {
							 | 
						||
| 
								 | 
							
								        pod2usage( -msg => "Version $VERSION\n", -exitval => 1 );
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								__END__
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
							 | 
						||
| 
								 | 
							
								#  Application Documentation
							 | 
						||
| 
								 | 
							
								#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
							 | 
						||
| 
								 | 
							
								#{{{
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 NAME
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								edit_series - edit one or more fields in the database for a given series
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 VERSION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This documentation refers to edit_series version 0.0.3
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 USAGE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    edit_series [-h] [-debug=N] [-[no]updatedb] [-series_id=ID] [-regexp]
							 | 
						||
| 
								 | 
							
								    [-[no]description] [-[no]private] [-[no]valid] [-config=FILE] series_name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    edit_series -updatedb -description GIMP
							 | 
						||
| 
								 | 
							
								    edit_series -updatedb -description -regexp Awk
							 | 
						||
| 
								 | 
							
								    edit_series -updatedb -noprivate -valid -series_id=102
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 REQUIRED ARGUMENTS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<series_name>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Unless the B<-series_id=ID> option is given (see the OPTIONS section) it is
							 | 
						||
| 
								 | 
							
								necessary to provide a series name.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Unless the B<-regexp> option is provided (see the OPTIONS section) the
							 | 
						||
| 
								 | 
							
								series name must match exactly, otherwise it is regarded as a MySQL regular
							 | 
						||
| 
								 | 
							
								expression.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 OPTIONS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]updatedb>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option is required to make the script apply any changes that are made to
							 | 
						||
| 
								 | 
							
								the database. By default no updates are applied (B<-noupdatedb>).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-regexp>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option causes the B<series_name> argument to be interpreted as a regular
							 | 
						||
| 
								 | 
							
								expression for the MySQL database. By default the argument is treated as if it
							 | 
						||
| 
								 | 
							
								is an exact match.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-series_id=ID>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The series can be specified by the series ID through this route. If this used then
							 | 
						||
| 
								 | 
							
								the B<series_name> argument is not required (and is ignored if given).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]description>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option, if given (as B<-description>), indicates that the 'description'
							 | 
						||
| 
								 | 
							
								field is to be edited.  The Vim editor is invoked to make changes. The default
							 | 
						||
| 
								 | 
							
								is B<-nodescription> meaning that this field is not to be edited.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]private>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option, if given (as B<-private>), indicates that the 'private' field is
							 | 
						||
| 
								 | 
							
								to be set to 'true'. If given as B<-noprivate> this field is set to 'false'.
							 | 
						||
| 
								 | 
							
								If omitted altogether then the field is not changed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-[no]valid>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option, if given (as B<-valid>), indicates that the 'valid' field is to
							 | 
						||
| 
								 | 
							
								be set to 'true'. If given as B<-novalid> this field is set to 'false'.
							 | 
						||
| 
								 | 
							
								If omitted altogether then the field is not changed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<-config=FILE>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This option allows an alternative configuration file to be used. This file
							 | 
						||
| 
								 | 
							
								defines the location of the database, its port, its name and the username and
							 | 
						||
| 
								 | 
							
								password to be used to access it. This feature was added to allow the script
							 | 
						||
| 
								 | 
							
								to access alternative databases or the live database over an SSH tunnel.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								See the CONFIGURATION AND ENVIRONMENT section below for the file format.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the option is omitted the default file is used: B<.hpr_db.cfg>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DESCRIPTION
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The script B<edit_series> provides an editor interface to certain fields in
							 | 
						||
| 
								 | 
							
								the B<miniseries> table of the HPR database. The fields are:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<description>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This field is stored in a 'TEXT' field in the database. It is possible for
							 | 
						||
| 
								 | 
							
								the field to contain HTML, and some series do. In general it is better to keep
							 | 
						||
| 
								 | 
							
								this field short since it is displayed in its entirety before the notes for
							 | 
						||
| 
								 | 
							
								each show in the series.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DIAGNOSTICS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=over 4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Select one of -description, -private and -valid>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								At least one of these options is required. This a fatal error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Specify the series name>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If no series name has been provided, and the B<-series_id=ID> option has not been
							 | 
						||
| 
								 | 
							
								used the script is unable to determine the series to edit. This is a fatal
							 | 
						||
| 
								 | 
							
								error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Unable to find ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The configuration file containing details of the database cannot be found.
							 | 
						||
| 
								 | 
							
								This is a fatal error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<DBI connect ... failed: Access denied for user ... >
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The database connection has been denied. Check the configuration details (see
							 | 
						||
| 
								 | 
							
								below). This a fatal error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Too many matches to regex ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								In B<-regex> mode a series name has been provided that matches too many series in
							 | 
						||
| 
								 | 
							
								the database. Try again with a less ambiguous name. This a fatal error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Unable to find series matching regex ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								In B<-regex> mode a series name has been provided that matches no qseries in the
							 | 
						||
| 
								 | 
							
								database. Try again. This a fatal error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Edit failed>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If the Vim edit session fails in some way the script reports it this way. This
							 | 
						||
| 
								 | 
							
								a fatal error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=item B<Field '...' is too long (...); truncated to ...>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The string provided for the field is greater than the limit and has been
							 | 
						||
| 
								 | 
							
								truncated. This is a warning.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=back
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 CONFIGURATION AND ENVIRONMENT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The script obtains the credentials it requires to open the HPR database from
							 | 
						||
| 
								 | 
							
								a configuration file. The name of the file it expects is B<.hpr_db.cfg> in the
							 | 
						||
| 
								 | 
							
								directory holding the script. To change this will require changing the script.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The configuration file format is as follows:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 <database>
							 | 
						||
| 
								 | 
							
								     host = 127.0.0.1
							 | 
						||
| 
								 | 
							
								     port = PORT
							 | 
						||
| 
								 | 
							
								     name = DATABASE
							 | 
						||
| 
								 | 
							
								     user = USERNAME
							 | 
						||
| 
								 | 
							
								     password = PASSWORD
							 | 
						||
| 
								 | 
							
								 </database>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 DEPENDENCIES
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								 Config::General
							 | 
						||
| 
								 | 
							
								 DBI
							 | 
						||
| 
								 | 
							
								 Data::Dumper
							 | 
						||
| 
								 | 
							
								 File::Slurper
							 | 
						||
| 
								 | 
							
								 File::Temp
							 | 
						||
| 
								 | 
							
								 Getopt::Long
							 | 
						||
| 
								 | 
							
								 Pod::Usage
							 | 
						||
| 
								 | 
							
								 SQL::Abstract
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 BUGS AND LIMITATIONS
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								There are no known bugs in this module.
							 | 
						||
| 
								 | 
							
								Please report problems to Dave Morriss  (Dave.Morriss@gmail.com)
							 | 
						||
| 
								 | 
							
								Patches are welcome.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 AUTHOR
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Dave Morriss (Dave.Morriss@gmail.com)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								=head1 LICENCE AND COPYRIGHT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Copyright (c) 2020 Dave Morriss (Dave.Morriss@gmail.com). All rights reserved.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								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]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
							 | 
						||
| 
								 | 
							
								
							 |