882 lines
29 KiB
Perl
Executable File
882 lines
29 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#===============================================================================
|
|
#
|
|
# FILE: edit_shownotes
|
|
#
|
|
# USAGE: ./edit_shownotes [-help] [-debug=N] [-field=NAME] -episode=N
|
|
#
|
|
# DESCRIPTION: Perform edits on metadata in show notes
|
|
#
|
|
# OPTIONS: ---
|
|
# REQUIREMENTS: ---
|
|
# BUGS: ---
|
|
# NOTES: 2022-12-20: originally written to update the shownotes.txt
|
|
# file as well as shownotes.json. Now we are phasing out the
|
|
# former and using just the JSON, so the former file is not
|
|
# being edited.
|
|
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
|
|
# VERSION: 0.0.5
|
|
# CREATED: 2022-12-07 13:05:40
|
|
# REVISION: 2024-04-27 22:41:24
|
|
#
|
|
#===============================================================================
|
|
|
|
use v5.36;
|
|
use strict;
|
|
use warnings;
|
|
use utf8;
|
|
use feature qw{ postderef say signatures state try };
|
|
no warnings
|
|
qw{ experimental::postderef experimental::signatures experimental::try };
|
|
|
|
use Getopt::Long;
|
|
use Pod::Usage qw{pod2usage};
|
|
|
|
use Term::ANSIColor;
|
|
use File::Temp;
|
|
use File::Slurper qw{ read_text write_text };
|
|
use File::Copy;
|
|
|
|
use JSON;
|
|
|
|
use Log::Handler;
|
|
|
|
use Data::Dumper;
|
|
|
|
#
|
|
# Version number (manually incremented)
|
|
#
|
|
our $VERSION = '0.0.5';
|
|
|
|
#
|
|
# 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/Show_Submission";
|
|
my $cache = "$basedir/shownotes";
|
|
my $logdir = "$basedir/logs";
|
|
my $logfile = "$logdir/${PROG}.log";
|
|
|
|
my ( $fh, $showno, $showid );
|
|
my ( $jsonfile, $jsonfile_bk, $json_text, $JSON_content );
|
|
my ( $before, $field_contents );
|
|
#my ( $txtfile, $txtfile_bk, $txtstring, @txtbuffer );
|
|
|
|
#
|
|
# Text colours
|
|
#
|
|
my $red = color('red');
|
|
my $green = color('green');
|
|
my $yellow = color('yellow');
|
|
my $magenta = color('magenta');
|
|
my $bold = color('bold');
|
|
my $reset = color('reset');
|
|
|
|
#
|
|
# How to find (and edit) the fields in shownotes.{txt,json}.
|
|
# The fields are 'tags', 'title' and 'summary'.
|
|
# If the JSON file is being edited the primary field offered to the editor is
|
|
# the first in the array (e.g. 'episode.Tags') and the other one simply echoes
|
|
# it.
|
|
# NOTE: 2022-12-20 - stopped modifying the shownotes.txt file
|
|
#
|
|
# If the TXT file is being updated (from the primary JSON version edited
|
|
# earlier) we find it in the array holding the text file using the 'regex'
|
|
# value, and build a new line using the 'label' value.
|
|
# This will be cleaned up when the TXT variant is dropped.
|
|
#
|
|
my %fields = (
|
|
'tags' => {
|
|
'json' => [ 'episode.Tags', 'metadata.POST.tags' ],
|
|
# 'txt' => { regex => qr{^Tags:}, label => 'Tags', },
|
|
},
|
|
'title' => {
|
|
'json' => [ 'episode.Title', 'metadata.POST.title' ],
|
|
# 'txt' => { regex => qr{^Title:}, label => 'Title', },
|
|
},
|
|
'summary' => {
|
|
'json' => [ 'episode.Summary', 'metadata.POST.summary' ],
|
|
# 'txt' => { regex => qr{^Summary:}, label => 'Summary', },
|
|
},
|
|
);
|
|
|
|
#
|
|
# Enable Unicode mode
|
|
#
|
|
binmode STDOUT, ":encoding(UTF-8)";
|
|
binmode STDERR, ":encoding(UTF-8)";
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Options and arguments
|
|
#-------------------------------------------------------------------------------
|
|
my $DEF_DEBUG = 0;
|
|
my $DEF_FIELD = 'tags';
|
|
|
|
#
|
|
# Process options
|
|
#
|
|
my %options;
|
|
Options( \%options );
|
|
|
|
#
|
|
# Default help
|
|
#
|
|
pod2usage(
|
|
-msg => "$PROG version $VERSION\n",
|
|
-verbose => 0,
|
|
-exitval => 1
|
|
) if ( $options{'help'} );
|
|
|
|
#
|
|
# Detailed help
|
|
#
|
|
pod2usage(
|
|
-msg => "$PROG version $VERSION\n",
|
|
-verbose => 2,
|
|
-exitval => 1,
|
|
) if ( $options{'documentation'} );
|
|
|
|
#
|
|
# Collect options
|
|
#
|
|
my $DEBUG = ( defined( $options{debug} ) ? $options{debug} : $DEF_DEBUG );
|
|
my $field = ( defined( $options{field} ) ? $options{field} : $DEF_FIELD );
|
|
|
|
my $test_dir = $options{test_dir};
|
|
|
|
$showno = $options{episode};
|
|
pod2usage(
|
|
-msg => "$PROG version $VERSION\nMissing mandatory option -episode\n",
|
|
-verbose => 0,
|
|
-exitval => 1
|
|
) unless $showno;
|
|
|
|
#
|
|
# Make an id in 'hpr1234' format
|
|
#
|
|
$showid = sprintf('hpr%04d',$showno);
|
|
|
|
#
|
|
# Check that the -field=FIELD option is valid
|
|
#
|
|
die "Invalid field specification: $field\n"
|
|
unless ($field =~ /^(tags|title|summary)$/);
|
|
|
|
#
|
|
# The files we'll parse and/or edit (and that we'll backup to). Check the
|
|
# former exist.
|
|
# If we've received a test directory we'll run in test mode
|
|
#
|
|
if ($test_dir) {
|
|
#
|
|
# We need a directory here so remove any file component.
|
|
#
|
|
die "Missing path $test_dir\n" unless ( -e $test_dir );
|
|
unless (-d $test_dir) {
|
|
#
|
|
# Trim a file path to what is likely to be a directory only, and test
|
|
# it exists. Add a trailing '/' if none.
|
|
#
|
|
$test_dir =~ s|/?[^/]*$||mx;
|
|
die "Missing directory $test_dir\n" unless ( -d $test_dir );
|
|
$test_dir .= '/' unless ($test_dir =~ qr{/$});
|
|
}
|
|
|
|
#
|
|
# Look in the test place
|
|
#
|
|
$jsonfile = "$test_dir/shownotes.json";
|
|
# $txtfile = "$test_dir/shownotes.txt";
|
|
|
|
$jsonfile_bk = "$test_dir/shownotes.json.orig";
|
|
# $txtfile_bk = "$test_dir/shownotes.txt.orig";
|
|
}
|
|
else {
|
|
#
|
|
# Look in the default place
|
|
#
|
|
$jsonfile = "$cache/$showid/shownotes.json";
|
|
# $txtfile = "$cache/$showid/shownotes.txt";
|
|
|
|
$jsonfile_bk = "$cache/$showid/shownotes.json.orig";
|
|
# $txtfile_bk = "$cache/$showid/shownotes.txt.orig";
|
|
}
|
|
|
|
die colored( "Unable to find JSON file $jsonfile", 'red' ) . "\n"
|
|
unless ( -e $jsonfile );
|
|
#die colored( "Unable to find text file $txtfile", 'red' ) . "\n"
|
|
# unless ( -e $txtfile );
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Set up logging keeping the default log layout except for the date. The format
|
|
# is "%T [%L] %m" where '%T' is the timestamp, '%L' is the log level and '%m is
|
|
# the message. We fiddle with this to implement a 'Test' mode.
|
|
#-------------------------------------------------------------------------------
|
|
my $log = Log::Handler->new();
|
|
|
|
$log->add(
|
|
file => {
|
|
timeformat => "%Y/%m/%d %H:%M:%S",
|
|
message_layout => (defined($test_dir) ? "%T [%L] TEST %m" : "%T [%L] %m"),
|
|
filename => $logfile,
|
|
minlevel => 0,
|
|
maxlevel => 7,
|
|
}
|
|
);
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Read the JSON input file and parse it into $JSON_content (hashref)
|
|
#-------------------------------------------------------------------------------
|
|
my $json = JSON->new->utf8;
|
|
open( $fh, '<:encoding(UTF-8)', $jsonfile );
|
|
$json_text = <$fh>;
|
|
close($fh);
|
|
|
|
#
|
|
# Careful! Bad JSON can crash the script here!
|
|
#
|
|
try {
|
|
$JSON_content = decode_json($json_text);
|
|
}
|
|
catch ($e) {
|
|
die colored( "Failed to decode the JSON in $jsonfile", 'red' ) . "\n"
|
|
}
|
|
|
|
$log->info( $showno, "Collecting $field from JSON" );
|
|
|
|
_debug( $DEBUG > 2, Dumper($JSON_content) );
|
|
#_debug( $DEBUG > 2, "\$field: $field" );
|
|
#_debug( $DEBUG > 2, Dumper(\%fields) );
|
|
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Get the contents of the primary JSON field. The group will all be the same,
|
|
# as will the text versions, so we edit the primary and propagate to the
|
|
# others.
|
|
#-------------------------------------------------------------------------------
|
|
$log->info( $showno,
|
|
"Collecting from JSON with path '$fields{$field}->{json}->[0]'" );
|
|
|
|
#
|
|
# Use the path in the %fields hash to get the prinmary node in the JSON hash.
|
|
# We are using 'get_JSON' rather than going to the field directly because
|
|
# we're hunting for different parts of the JSON structure depending on the
|
|
# -field=NAME option and using a jq-like path stored in %fields.
|
|
#
|
|
$field_contents = get_JSON( $JSON_content, $fields{$field}->{json}->[0] );
|
|
#say( defined($field_contents) ? $field_contents : 'undefined' );
|
|
die colored( "No contents in field '$field'. Can't continue", 'red' ) . "\n"
|
|
unless ( defined($field_contents) );
|
|
|
|
$log->info( $showno, "Contents of field: $field_contents" );
|
|
$before = $field_contents;
|
|
|
|
#
|
|
# Run the editor on what we collected
|
|
#
|
|
if ( $field eq 'tags' ) {
|
|
$field_contents
|
|
= check_field( $field, run_editor($field_contents), 200, qr{(\n)} );
|
|
}
|
|
elsif ( $field eq 'title' ) {
|
|
$field_contents
|
|
= check_field( $field, run_editor($field_contents), 100, qr{(\n)} );
|
|
}
|
|
elsif ( $field eq 'summary' ) {
|
|
$field_contents
|
|
= check_field( $field, run_editor($field_contents), 100, qr{(\n)} );
|
|
}
|
|
|
|
die colored( "Nothing was changed. Exiting", 'red' ) . "\n"
|
|
if ( $field_contents eq $before );
|
|
|
|
$log->info( $showno, "New contents of field: $field_contents" );
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Load the text version of the metadata
|
|
# NOTE: 2022-12-20 - stopped modifying the shownotes.txt file
|
|
#-------------------------------------------------------------------------------
|
|
#open( $fh, '<:encoding(UTF-8)', $txtfile );
|
|
#@txtbuffer = <$fh>;
|
|
#close($fh);
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Make the changes to all the relevant points in the two data structures.
|
|
# NOTE: 2022-12-20 - stopped modifying the shownotes.txt file
|
|
#-------------------------------------------------------------------------------
|
|
put_JSON( $JSON_content, $fields{$field}->{json}->[0], $field_contents );
|
|
put_JSON( $JSON_content, $fields{$field}->{json}->[1], $field_contents );
|
|
#_debug( $DEBUG > 1, Dumper($JSON_content) );
|
|
|
|
$log->info( $showno, "Updated JSON '$field' fields" );
|
|
|
|
#@txtbuffer = update_txt( \@txtbuffer, $fields{$field}->{txt}->{regex},
|
|
# $fields{$field}->{txt}->{label} . ":\t$field_contents\n" );
|
|
#_debug( $DEBUG > 1, "Text: " . Dumper( \@txtbuffer ) );
|
|
|
|
#$log->info( $showno, "Updated text '$field' fields" );
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Turn the data structures back into files, backing up the originals and
|
|
# writing the new.
|
|
#-------------------------------------------------------------------------------
|
|
unless ( -e $jsonfile_bk ) {
|
|
copy ($jsonfile, $jsonfile_bk) or die "Unable to backup $jsonfile\n";
|
|
$log->info( $showno, "JSON backup created" );
|
|
}
|
|
$json_text = encode_json($JSON_content);
|
|
write_text($jsonfile,$json_text);
|
|
|
|
$log->info( $showno, "JSON file updated" );
|
|
|
|
#unless ( -e $txtfile_bk ) {
|
|
# copy ($txtfile, $txtfile_bk) or die "Unable to backup $txtfile\n";
|
|
# $log->info( $showno, "Text backup created" );
|
|
#}
|
|
#$txtstring = join('',@txtbuffer);
|
|
#write_text($txtfile,$txtstring);
|
|
#
|
|
#$log->info( $showno, "Text file updated" );
|
|
|
|
exit;
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: get_JSON
|
|
# PURPOSE: Traverse a data structure using dotted notation and return the
|
|
# value at the end point
|
|
# PARAMETERS: $hash hashref to traverse
|
|
# $path a path in the form of '.key1.key2.[0]' which
|
|
# should walk to the key 'key1', then 'key2'
|
|
# within it, and open the array expected to be
|
|
# there and get element 0.
|
|
# RETURNS: The value found or undef
|
|
# DESCRIPTION: Uses 'traverse_JSON' to find the $path in the $hash, returning
|
|
# a pointer (reference) to it. This can then be de-referenced to
|
|
# return the item being referenced - unless it's undefined in
|
|
# which case that value is returned. Being undefined means that
|
|
# the path did not match the structure of the hash.
|
|
# THROWS: No exceptions
|
|
# COMMENTS: None
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub get_JSON {
|
|
my ( $hash, $path ) = @_;
|
|
|
|
my $pointer = traverse_JSON( $hash, $path );
|
|
|
|
#
|
|
# If undef then need to return undef, not try and deref it!
|
|
#
|
|
if (defined($pointer)) {
|
|
return $$pointer;
|
|
}
|
|
return;
|
|
}
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: put_JSON
|
|
# PURPOSE: Traverse a data structure using dotted notation and update the
|
|
# value at the end point
|
|
# PARAMETERS: $hash hashref to traverse
|
|
# $path a path in the form of '.key1.key2.[0]' which
|
|
# should walk to the key 'key1', then 'key2'
|
|
# within it, and open the array expected to be
|
|
# there and rewrite element 0.
|
|
# $new_value the new value to place at the point in the
|
|
# hash
|
|
# RETURNS: The previous value if found, or undef
|
|
# DESCRIPTION: Uses 'traverse_JSON' to find the $path in the $hash, returning
|
|
# a pointer (reference) to it. This can then be de-referenced to
|
|
# return the item being referenced - unless it's undefined in
|
|
# which case that value is returned. Being undefined means that
|
|
# the path did not match the structure of the hash. Finally, if
|
|
# the reference is valid then it's used to save the contents of
|
|
# $new_value.
|
|
# THROWS: No exceptions
|
|
# COMMENTS: None
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub put_JSON {
|
|
my ( $hash, $path, $new_value ) = @_;
|
|
|
|
my $pointer = traverse_JSON( $hash, $path );
|
|
my $old_value;
|
|
|
|
#
|
|
# If $pointer is defined then dereference it as the return value. Also,
|
|
# use it to write the new value.
|
|
#
|
|
if (defined($pointer)) {
|
|
$old_value = $$pointer;
|
|
$$pointer = $new_value;
|
|
}
|
|
|
|
return $old_value;
|
|
}
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: traverse_JSON
|
|
# PURPOSE: Traverse a data structure using dotted notation
|
|
# PARAMETERS: $hash hashref to traverse
|
|
# $path a path in the form of '.key1.key2.[0]' which
|
|
# should walk to the key 'key1', then 'key2'
|
|
# within it, and open the array expected to be
|
|
# there and return a pointer to element 0.
|
|
# RETURNS: A reference to the end point of the path, or undef if not
|
|
# found.
|
|
# DESCRIPTION: Given a path like '.metadata.POST.title' (where the leading
|
|
# '.' is optional because it's ignored), this is used to
|
|
# traverse a Perl version of a JSON data structure to return
|
|
# a *pointer* (reference) to a node.
|
|
#
|
|
# This is done by processing the path step by step and using
|
|
# 'name' elements as hash keys to get to the next level or
|
|
# return a terminal hash element. We also cater for '.[0]'
|
|
# format path elements which means to use the zeroth member of
|
|
# an array. Thus, if the tags were represented as an array rather
|
|
# than a scalar CSV string, we'd be able to use a path like:
|
|
# 'episode.Tags.[0]' to return the first tag.
|
|
#
|
|
# The data structure traversal is performed by using a pointer
|
|
# to a part of it, and since nested hashes are constructed by
|
|
# using hashrefs as hash element values, we get back references
|
|
# by default.
|
|
#
|
|
# Once the terminal node has been reached it may be a scalar
|
|
# (very likely in this application), so we make and return
|
|
# a reference to it so that the caller can use this reference in
|
|
# a standard way to return or change the contents.
|
|
#
|
|
# This function is meant to be more generic than this
|
|
# application requires. The JSON we're dealing with here doesn't
|
|
# have arrays (at the moment). This description is
|
|
# application-specifc however!
|
|
# THROWS: No exceptions
|
|
# COMMENTS: None
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub traverse_JSON {
|
|
my ( $hash, $path ) = @_;
|
|
|
|
my ( $pointer, $index );
|
|
my @elements = split( '\.', $path );
|
|
|
|
#
|
|
# Because $hash is a hashref we can just copy it
|
|
#
|
|
$pointer = $hash;
|
|
|
|
#
|
|
# Traverse the hash using the path
|
|
#
|
|
foreach my $element (@elements) {
|
|
next unless defined($element);
|
|
|
|
#
|
|
# Plain names are keys or scalar end nodes, bracketed things are
|
|
# arrays (which can be scalars of course)
|
|
#
|
|
unless ( ($index) = ( $element =~ /\[([^]])\]/ ) ) {
|
|
# Hash key
|
|
if ( exists( $pointer->{$element} ) ) {
|
|
if ( ref( $pointer->{$element} ) eq '' ) {
|
|
#
|
|
# It's not a refererence so make a reference to it
|
|
#
|
|
$pointer = \$pointer->{$element};
|
|
}
|
|
else {
|
|
#
|
|
# It's a reference
|
|
#
|
|
$pointer = $pointer->{$element};
|
|
}
|
|
}
|
|
else {
|
|
#
|
|
# Doesn't exist!
|
|
#
|
|
return; #undef
|
|
}
|
|
}
|
|
else {
|
|
# Array
|
|
if ( exists( $pointer->[$index] ) ) {
|
|
if ( ref( $pointer->[$index] ) eq '' ) {
|
|
#
|
|
# It's not a refererence so make a reference to it
|
|
#
|
|
$pointer = \$pointer->[$index];
|
|
}
|
|
else {
|
|
#
|
|
# It's a reference
|
|
#
|
|
$pointer = $pointer->[$index];
|
|
}
|
|
}
|
|
else {
|
|
#
|
|
# Doesn't exist!
|
|
#
|
|
return; # undef
|
|
}
|
|
}
|
|
}
|
|
return $pointer;
|
|
}
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: update_txt
|
|
# PURPOSE: Update the data structure from 'shownotes.txt' in the light of
|
|
# requested changes.
|
|
# PARAMETERS: $array arrayref to the contents of the shownotes.txt
|
|
# file
|
|
# $regex regular expression to find the line that needs
|
|
# to be changed (there's just one main one).
|
|
# $new_value new value to replace the original line
|
|
# RETURNS: Modified array
|
|
# DESCRIPTION: Finds the element in the arrayref ($array) which matches the
|
|
# regular expression ($regex). When found it is replaced by the
|
|
# new value ($new_value). The modified buffer is returned as
|
|
# a list. Uses $result to determine the value of each line since
|
|
# it is not permitted to change the $_ variable in the 'map'
|
|
# statement.
|
|
# THROWS: No exceptions
|
|
# COMMENTS: None
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub update_txt {
|
|
my ( $array, $regex, $new_value ) = @_;
|
|
|
|
my $result;
|
|
my @newbuffer
|
|
= map { $result = ( $_ =~ /$regex/ ? $new_value : $_ ); $result }
|
|
@$array;
|
|
|
|
return @newbuffer;
|
|
}
|
|
|
|
#=== 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: coalesce
|
|
# PURPOSE: To find the first defined argument and return it
|
|
# PARAMETERS: Arbitrary number of arguments
|
|
# RETURNS: The first defined argument or undef if there are none
|
|
# DESCRIPTION:
|
|
# THROWS: No exceptions
|
|
# COMMENTS: None
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub coalesce {
|
|
foreach (@_) {
|
|
return $_ if defined($_);
|
|
}
|
|
return undef; ## no critic
|
|
}
|
|
|
|
#=== 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|manpage",
|
|
"debug=i", "field=s",
|
|
"episode=i", "test_dir=s",
|
|
);
|
|
|
|
if ( !GetOptions( $optref, @options ) ) {
|
|
pod2usage(
|
|
-msg => "$PROG version $VERSION\n",
|
|
-verbose => 0,
|
|
-exitval => 1
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
__END__
|
|
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
# Application Documentation
|
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
#{{{
|
|
|
|
=head1 NAME
|
|
|
|
edit_shownotes - edit JSON and text shownote metadata
|
|
|
|
=head1 VERSION
|
|
|
|
This documentation refers to edit_shownotes version 0.0.5
|
|
|
|
=head1 USAGE
|
|
|
|
./edit_shownotes [-help] [-documentation|manpage] [-debug=N]
|
|
[-field=FIELD] -episode=N
|
|
|
|
|
|
./edit_shownotes -field=tags -episode=3754
|
|
./edit_shownotes -field=summary -episode=3762 \
|
|
-test_dir=$PWD/tests/shownotes/hpr3762
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 8
|
|
|
|
=item B<-help>
|
|
|
|
Prints a brief help message describing the usage of the program, and then exits.
|
|
|
|
=item B<-manpage> or B<-documentation>
|
|
|
|
Prints the entire documentation for the script.
|
|
|
|
=item B<-field=FIELD>
|
|
|
|
This optional parameter defaults to B<-field=tags>. The only permitted fields
|
|
are 'tags', 'title' and 'summary'.
|
|
|
|
=item B<-episode=N>
|
|
|
|
This mandatory option specifies the number of the HPR episode that is being
|
|
parsed. It needs to be numeric even though it is often used with an 'hpr'
|
|
prefix internally. Leading zeroes are not required (when these are
|
|
appropriate).
|
|
|
|
=item B<-test_dir=PATH>
|
|
|
|
This option is for testing. Normally the script determines the path to the
|
|
file(s) it will parse and update. It actually finds the field specified by the
|
|
B<-field-FIELD> option in the JSON file B<shownotes.json> and calls the editor
|
|
to change this, then propagates any changes to the other JSON instance and the
|
|
one in B<shownotes.txt>.
|
|
|
|
This option has been provided to make it simpler to make copies of the files
|
|
relating to an incoming show and use them to test changes to the script.
|
|
|
|
The current workflow expects the "live" files in
|
|
B<$HOME/HPR/Show_Submission/shownotes/hprSHOW>, but there is another area
|
|
where show files are placed for testing:
|
|
B<$HOME/HPR/Show_Submission/tests/shownotes/hprSHOW>.
|
|
|
|
It is simply a matter of making a copy and of referencing the directory path
|
|
to this option. Changes will be made to this copy and log records written, but
|
|
these will contain the word 'TEST' to show that the script is being run in
|
|
this mode.
|
|
|
|
=item B<-debug=N>
|
|
|
|
Causes certain debugging information to be displayed.
|
|
|
|
0 (the default) no debug output
|
|
1 N/A
|
|
2 TBA
|
|
3 TBA
|
|
|
|
=back
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This script is meant to be a component in a workflow dealing with the notes
|
|
submitted by HPR hosts with the shows they are uploading.
|
|
|
|
The data from the web form which is used by hosts submitting shows is received
|
|
in two formats, with fixed names.
|
|
|
|
The file B<shownotes.txt> contains the form data in a label/value format, but
|
|
also uses PHP dumps to show some of the metadata. It is no longer used by the
|
|
current workflow, but is retained for historical reasons.
|
|
|
|
The alternative file format is JSON, which is stored in B<shownotes.json>.
|
|
This contains the same data as the other file but in a more standardised
|
|
format.
|
|
|
|
This script is designed to allow the editing of three specific fields in these
|
|
two files. This is because these fields are not used in the show note
|
|
preparation workflow (though they are referenced), but any changes can be
|
|
propagated back to the HPR server to allow workflows there to use the
|
|
corrected version(s).
|
|
|
|
The fields which can be edited are duplicated in the two files. One of these
|
|
is edited, and if appropriate, is propagated to the other duplicates. This
|
|
simplifies the editing of the text format, and the JSON format, which is not
|
|
simple to edit in a conventional way.
|
|
|
|
=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
|
|
|
|
Data::Dumper
|
|
File::Slurper
|
|
File::Temp
|
|
Getopt::Long
|
|
JSON
|
|
Log::Handler
|
|
Pod::Usage
|
|
Term::ANSIColor
|
|
|
|
=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) 2022 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
|
|
|