Updates for Community News tools

Community_News/make_email: minor tidying

Community_News/make_shownotes: addition of File::Copy; addition of cache
    management routines; cache is now managed here as well as in
    'make_email'; fixed a bug with use of '$/'; POD enhancements.

Community_News/recording_dates.dat: for reference

Community_News/shownote_template12.tpl: removed the "Updated on" section.

Community_News/shownotes_container.tpl: enhanced the datestamp header
    when generating "full" shownotes.
This commit is contained in:
Dave Morriss
2025-08-18 10:31:37 +01:00
parent 89b51b4406
commit a6ec5c095e
5 changed files with 236 additions and 44 deletions

View File

@@ -87,7 +87,7 @@ our $VERSION = '0.3.4';
( my $basedir = abs_path($0) ) =~ s|/?[^/]*$||mx; ( my $basedir = abs_path($0) ) =~ s|/?[^/]*$||mx;
my $configfile = "$basedir/.${PROG}.cfg"; my $configfile = "$basedir/.${PROG}.cfg";
my ( %recdates, $rdfh ); my ( %recdates );
# #
# Run in the script's directory # Run in the script's directory
@@ -627,7 +627,7 @@ elsif ( ! -e $cfg_recdatefile) {
# Load the recording dates # Load the recording dates
# #
if ($cfg_recdatefile) { if ($cfg_recdatefile) {
%recdates = load_cache($cfg_recdatefile, $rdfh); %recdates = load_cache($cfg_recdatefile);
} }
_debug($DEBUG >= 2, _debug($DEBUG >= 2,

View File

@@ -29,10 +29,10 @@
# Where pod2pdf comes from App::pod2pdf # Where pod2pdf comes from App::pod2pdf
# #
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com # AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
# VERSION: 0.4.4 # VERSION: 0.4.5
# ORIGINAL: 2014-04-24 16:08:30 # ORIGINAL: 2014-04-24 16:08:30
# CREATED: 2025-03-13 15:07:35 # CREATED: 2025-03-13 15:07:35
# REVISION: 2025-04-17 15:12:27 # REVISION: 2025-08-09 11:28:22
# #
#=============================================================================== #===============================================================================
@@ -51,6 +51,8 @@ use Pod::Usage qw(pod2usage); # Use colour-capable Pod::Text
use Config::General; use Config::General;
use File::Copy;
use Date::Parse; use Date::Parse;
use Date::Calc qw{:all}; use Date::Calc qw{:all};
use DateTime; use DateTime;
@@ -71,7 +73,7 @@ use Data::Dumper;
# #
# Version number (manually incremented) # Version number (manually incremented)
# #
our $VERSION = '0.4.4'; our $VERSION = '0.4.5';
# #
# Various constants # Various constants
@@ -106,8 +108,7 @@ my $defcontainer = "$basedir/shownotes_container.tpl";
my ( $dbh, $sth1, $h1 ); my ( $dbh, $sth1, $h1 );
my ( @review_month, $releasedate, @releasedate, $hosts, $shows, $episode ); my ( @review_month, $releasedate, @releasedate, $hosts, $shows, $episode );
my ( @dc_lr, $dt_lr, @dc_lm, $dt_lm, @dc_rd, $dt_rd ); my ( @dc_lr, $dt_lr, @dc_lm, $dt_lm, @dc_rd, $dt_rd );
my ( %attributes ); my ( %attributes, %date_cache, $lr_option_status, $date_offset, @deftime );
my ( %date_cache, $date_offset, @deftime );
my ( $t_time, $missed_comments, $missed_count ); my ( $t_time, $missed_comments, $missed_count );
my ( $comments, $comment_count, $past_count, $ignore_count ); my ( $comments, $comment_count, $past_count, $ignore_count );
my ( %past, %current ); my ( %past, %current );
@@ -195,8 +196,8 @@ my $json_outfile = $options{json};
die "Unable to find configuration file $cfgfile\n" unless ( -e $cfgfile ); die "Unable to find configuration file $cfgfile\n" unless ( -e $cfgfile );
# #
# We're receiving the datetime for the last recording (that's the recording # If we're receiving the datetime for the last recording (that's the recording
# for the previous month), which isn't appropriate unless we're marking # for the previous month), it isn't appropriate unless we're marking
# comments. # comments.
# #
if (defined($lastrecording)) { if (defined($lastrecording)) {
@@ -204,6 +205,11 @@ if (defined($lastrecording)) {
unless defined($full_html_outfile); unless defined($full_html_outfile);
} }
#
# Record whether or not we got a -lastrecording=DATETIME option
#
$lr_option_status = defined($lastrecording);
# #
# One at least of the output files must be present # One at least of the output files must be present
# #
@@ -213,7 +219,7 @@ unless ($full_html_outfile || $html_outfile || $json_outfile) {
die "Missing output file option\n"; die "Missing output file option\n";
} }
say "DEBUG level is %d", $DEBUG if $DEBUG > 0; say "DEBUG level is $DEBUG" if $DEBUG > 0;
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Use the date provided for the review month or use today's date as the default # Use the date provided for the review month or use today's date as the default
@@ -231,6 +237,7 @@ else {
@review_month = Today(); @review_month = Today();
} }
$review_month[2] = 1; $review_month[2] = 1;
_debug( $DEBUG > 1, '@review_month = ' . Dumper( \@review_month ) );
@dc_lm = @review_month; # TODO: Is this right? @dc_lm = @review_month; # TODO: Is this right?
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
@@ -291,6 +298,11 @@ $tags = [$tags] unless ( ref($tags) eq 'ARRAY' );
@deftime = split( ':', $start_time ); @deftime = split( ':', $start_time );
my $release_dow = Decode_Day_of_Week($release_day); my $release_dow = Decode_Day_of_Week($release_day);
#
# Report the date cache name before working on it.
#
emit( $silent, "Date cache: ", $date_cache_name, "\n" );
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Set last recording date and time from option or cache # Set last recording date and time from option or cache
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
@@ -298,18 +310,38 @@ my $release_dow = Decode_Day_of_Week($release_day);
# recording for the previous month) as an option, or we'll check the # recording for the previous month) as an option, or we'll check the
# cache. # cache.
# #
if (defined($lastrecording)) { if ($lr_option_status) {
# #
# Parse and perform rudimentary validation on the -lastrecording option # The -lastrecording option is present, so parse and perform rudimentary
# validation on the values.
# #
emit( $silent, "Last recording from option: ", $lastrecording, "\n" ); emit( $silent, "Last recording from option: ", $lastrecording, "\n" );
_debug( $DEBUG > 1, '$lastrecording = ' . $lastrecording ); _debug( $DEBUG > 1, '$lastrecording = ' . $lastrecording );
# Compute the last month, with a date # Compute the last month, with a date
@dc_lm = find_last_month(\@review_month); @dc_lm = find_last_month(\@review_month);
_debug( $DEBUG > 1, '@dc_lm = ' . Dumper( \@dc_lm ) );
@dc_lr = parse_to_dc( $lastrecording, \@deftime ); @dc_lr = parse_to_dc( $lastrecording, \@deftime );
_debug( $DEBUG > 1, '@dc_lr = ' . Dumper( \@dc_lr ) ); _debug( $DEBUG > 1, '@dc_lr = ' . Dumper( \@dc_lr ) );
#
# Validate the provided lastrecording date which should be a few days
# before the start of the review month or into the next month. This will
# make it between 26 and 35 days from the start of the previous month.
#
my $lr_delta_days
= abs( Delta_Days( @dc_lm[ 0 .. 2 ], @dc_lr[ 0 .. 2 ] ) );
_debug(
$DEBUG > 1,
'Difference between @dc_lm and @dc_lr: ' . $lr_delta_days
);
if ($lr_delta_days < 26 || $lr_delta_days > 35) {
say "Problem with -lastrecording=DATETIME specification.";
say "Difference between given date and start of the month before ",
"the review month is $lr_delta_days days";
die "Can't continue\n";
}
} }
else { else {
emit( $silent, "Getting last recording from cache\n" ); emit( $silent, "Getting last recording from cache\n" );
@@ -317,7 +349,6 @@ else {
# #
# Load the cache # Load the cache
# #
emit( $silent, "Date cache: ", $date_cache_name, "\n" );
%date_cache = load_cache($date_cache_name); %date_cache = load_cache($date_cache_name);
#_debug( $DEBUG > 1, '%date_cache = ' . Dumper(\%date_cache) ); #_debug( $DEBUG > 1, '%date_cache = ' . Dumper(\%date_cache) );
@@ -331,8 +362,8 @@ else {
# Abort if the cache didn't have the date # Abort if the cache didn't have the date
# #
unless (defined($lastrecording)) { unless (defined($lastrecording)) {
say "The date and time of the last recording is not in the cache"; say "The date and time of the last recording is not in the cache. Use";
say "Use option -lastrecording=DATETIME (or -lr=DATETIME) instead"; say "option -lastrecording=DATETIME (or -lr=DATETIME) to define them.";
die "Can't continue\n"; die "Can't continue\n";
} }
@@ -752,7 +783,7 @@ if ($show_comments) {
# #
if ( $full_html_outfile ) { if ( $full_html_outfile ) {
{ {
$/ = ''; local $/ = '';
chomp($row->{comment_text}); # NOTE: experimental chomp($row->{comment_text}); # NOTE: experimental
} }
} }
@@ -909,6 +940,48 @@ my $tt = Template->new(
} }
); );
#-------------------------------------------------------------------------------
# Update the cache from the -lastrecording=DATETIME option if needed
#-------------------------------------------------------------------------------
if ($lr_option_status) {
#
# We were given the last recording as an option. The most likely reason is
# that it's not in the cache, but it may contain a correction. Look to
# see if it is in the cache, and if so, whether it's the same. Add it if
# it's missing or update it unless it agrees.
#
emit( $silent, "Loading recording date cache\n" );
#
# Load the cache
#
%date_cache = load_cache($date_cache_name);
#_debug( $DEBUG > 1, '%date_cache = ' . Dumper(\%date_cache) );
#
# Create the month key from the month before the review month, then see if
# it's already in the cache. The date of the start of last month is in
# $dt_lm.
#
my $monthkey = sprintf( "%d-%02d-01", $dt_lm->year, $dt_lm->month );
#
# If the key is not in the %date_cache OR if it's already there but the
# value part doesn't match the last recording specification run
# update_cache to make changes. The tests are made in this order so we
# don't try and reference a non-existent element.
#
if (!exists( $date_cache{$monthkey} )
|| ( exists( $date_cache{$monthkey} )
&& ( $date_cache{$monthkey} ne $lastrecording ) )
)
{
$date_cache{$monthkey} = $lastrecording;
update_cache( $date_cache_name, \%date_cache );
emit( $silent, "Updated date cache\n" );
}
}
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# Generate the HTML fragment and add it to the JSON if that output is requested # Generate the HTML fragment and add it to the JSON if that output is requested
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
@@ -1307,7 +1380,14 @@ sub dc_to_dt {
# PURPOSE: Load the date cache into a hash # PURPOSE: Load the date cache into a hash
# PARAMETERS: $cache_name Name of file holding the cache # PARAMETERS: $cache_name Name of file holding the cache
# RETURNS: Contents of cache as a hash # RETURNS: Contents of cache as a hash
# DESCRIPTION: # DESCRIPTION: Opens the nominated file, parses each record, and adds the
# data to a hash. The record should contain the following:
# * 'YYYY-MM-01' the month for which the details are being
# recorded
# * ',' comma field separator
# * 'YYYY-MM-DD HH:MM:SS' timestamp of the recording
# The file is closed once it has been scanned. The function
# returns the completed hash.
# THROWS: No exceptions # THROWS: No exceptions
# COMMENTS: None # COMMENTS: None
# SEE ALSO: N/A # SEE ALSO: N/A
@@ -1317,24 +1397,102 @@ sub load_cache {
my ( $month, $datetime, %result ); my ( $month, $datetime, %result );
open( my $dc, '<', $cache_name ) #
or die "$0 : failed to open '$cache_name': $!\n"; # Open the file in read mode
#
open( my $dcfh, '<', $cache_name )
or die "$PROG: failed to open '$cache_name': $!\n";
while ( my $line = <$dc> ) { while ( my $line = <$dcfh> ) {
chomp($line); chomp($line);
if ( ( $month, $datetime ) if ( ( $month, $datetime )
= ( $line =~ /^(\d{4}-\d{2}-\d{2}),(.*)$/ ) ) = ( $line =~ /^(\d{4}-\d{2}-\d{2}),(.*)$/ ) )
{ {
$result{$month} = $datetime; $result{$month} = $datetime;
} }
# TODO: Report any errors found in the file
} }
close($dc) close($dcfh)
or warn "$0 : failed to close '$cache_name': $!\n"; or warn "${PROG}: failed to close '$cache_name': $!\n";
return %result; return %result;
} }
#=== FUNCTION ================================================================
# NAME: append_cache
# PURPOSE: Append a new line to the cache
# PARAMETERS: $cache_name Name of file holding the cache
# $line New record to add
# RETURNS: Nothing
# DESCRIPTION: Opens the nominated file and appends the new record in $line.
# The file is closed once it has been updated.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub append_cache {
my ( $cache_name, $line ) = @_;
#
# Open the file in append mode
#
open( my $dcfh, '>>', $cache_name )
or die "$PROG: failed to open '$cache_name': $!\n";
say $dcfh $line;
close($dcfh)
or warn "${PROG}: failed to close '$cache_name': $!\n";
}
#=== FUNCTION ================================================================
# NAME: update_cache
# PURPOSE: Make changes to an existing line in the cache
# PARAMETERS: $cache_name Name of file holding the cache
# $rhash Hashref holding the updated cache contents
# RETURNS: Nothing
# DESCRIPTION: Makes a backup of the nominated file. Opens, truncates it and
# positions for writing (using 'seek'). The now empty file is
# filled with data from the hash and closed.
# THROWS: No exceptions
# COMMENTS: Uses 'copy' from File::Copy
# SEE ALSO: N/A
#===============================================================================
sub update_cache {
my ( $cache_name, $rhash ) = @_;
#
# Copy the cache file to a backup
#
copy($cache_name,"${cache_name}~")
or die "Unable to back up '$cache_name'\n";
#
# Open the original file in write mode
#
open( my $dcfh, '>', $cache_name )
or die "${PROG}: failed to open '$cache_name': $!\n";
#
# Truncate the file and seek to the start again
#
truncate($dcfh,0)
or die "${PROG}: failed to truncate '$cache_name': $!\n";
seek($dcfh,0,0)
or die "$PROG: failed to seek in '$cache_name': $!\n";
#
# Write the cache data to the file
#
for my $key (sort(keys(%$rhash))) {
say $dcfh sprintf("%s,%s",$key, $rhash->{$key});
}
close($dcfh)
or warn "${PROG}: failed to close '$cache_name': $!\n";
}
#=== FUNCTION ================================================================ #=== FUNCTION ================================================================
# NAME: find_last_month # NAME: find_last_month
# PURPOSE: Finds the previous month for working out marks # PURPOSE: Finds the previous month for working out marks
@@ -1811,7 +1969,7 @@ make_shownotes - Make show notes for the Hacker Public Radio Community News show
=head1 VERSION =head1 VERSION
This documentation refers to B<make_shownotes> version 0.4.4 This documentation refers to B<make_shownotes> version 0.4.5
=head1 USAGE =head1 USAGE
@@ -1924,19 +2082,49 @@ This will cause the generation of the file:
=item B<-lastrecording=DATETIME> or B<-lr=DATETIME> =item B<-lastrecording=DATETIME> or B<-lr=DATETIME>
As mentioned for B<-full-html=FILE>, the script needs the date of the last As mentioned for B<-full-html=FILE>, and later in the I<MARKING COMMENTS>
recording when marking comments. This can be extracted from the file section, the script needs the date of the last recording when marking
referenced in the configuration data using the setting B<cache>. By default comments. This can be extracted from the file referenced in the configuration
the name of this file is B<recording_dates.dat>, and its contents are managed data using the setting B<cache>. By default the name of this file is
when the script B<make_email> is run. B<recording_dates.dat>, and its contents are managed when the script
B<make_email> is run and by this script.
If for any reason the date and time of the last recording is missing, these If for any reason the date and time of the last recording is missing, these
values can be defined with this option. values can be defined with this option, and these values will be written to
the cache file (or modified, if necessary).
The format can be an ISO 8601 date followed by a 24-hour time, such as The format can be an ISO 8601 date followed by a 24-hour time, such as
'2020-01-25 15:00'. If the time is omitted it defaults to the value of '2020-01-25 15:00:00'. If the time is omitted it defaults to the value of
I<starttime> in the configuration file. I<starttime> in the configuration file.
The script will update the cache file with the date and time used in this
option if the relevant entry is missing. Also, if an entry is present but the
values are different from those provided with the option, the relevant entry
will be updated.
Note that the B<DATETIME> value must contain the date of the last recording.
This will be checked, and written to the cache file prefixed by a "key"
consisting of the first day of the month I<BEFORE> the month being reviewed.
For example, when generating the notes for August 2025 the following command
will be needed if there is no last recording date (for July 2025) in the
cache:
./make_shownotes -from=2025-08-01 -full-html=full_shownotes_%s.html \
-mail -comments -lr="2025-08-01 15:00:00"
Here we need the last recording date for the show reviewing HPR shows in July
2025. The date and time for this recording was in early August (Friday before
the first Monday of September 2025-09-01), as shown. This combination will
result in the addition of the following line to the cache file:
2025-07-01,2025-08-01 15:00:00
As mentioned, the addition of such date and time information to the cache will
normally be performed by B<make_email>, which performs the date computations
itself, unlike this script. This feature in this script is an alternative for
special cases.
=item B<-[no]silent> =item B<-[no]silent>
This option controls whether the script reports details of its progress This option controls whether the script reports details of its progress
@@ -2501,6 +2689,7 @@ Modules used:
Date::Parse Date::Parse
DateTime::Duration DateTime::Duration
DateTime DateTime
File::Copy
Getopt::Long Getopt::Long
HTML::Entities HTML::Entities
JSON JSON

View File

@@ -31,4 +31,8 @@
2025-01-01,2025-01-31 15:00:00 2025-01-01,2025-01-31 15:00:00
2025-02-01,2025-02-28 16:00:00 2025-02-01,2025-02-28 16:00:00
2025-03-01,2025-04-04 16:00:00 2025-03-01,2025-04-04 16:00:00
2025-04-01,2025-05-02 16:00:00 2025-04-01,2025-05-02 15:00:00
2025-05-01,2025-05-30 15:00:00
2025-06-01,2025-07-04 15:00:00
2025-07-01,2025-08-01 15:00:00
2025-08-01,2025-08-29 15:00:00

View File

@@ -161,9 +161,6 @@ by <a href="[% correspondents %]/[% pad4(arr.0.hostid) %].html" target="_blank">
</li> </li>
[%- END -%] [%- END -%]
</ul> </ul>
[%- IF mark_comments == 1 || ctext == 1 -%]
<p><small><small><em>Updated on [% date.format(date.now,'%Y-%m-%d %H:%M:%S') %]</em></small></small></p>
[%- END -%]
[%- END %] [%- END %]
[%# ---------------------------------------------------------------------------------------- -%] [%# ---------------------------------------------------------------------------------------- -%]
[% cc = (comment_count - past_count) -%] [% cc = (comment_count - past_count) -%]

View File

@@ -1,5 +1,6 @@
[%# /home/cendjm/HPR/Community_News/shownotes_container.tpl 2024-06-22 -%] [%# /home/cendjm/HPR/Community_News/shownotes_container.tpl 2024-06-22 -%]
[%# Container to display Community News shownotes as an HTML page -%] [%# Container to display Community News shownotes as an HTML page -%]
[%- USE date -%]
[% DEFAULT shownotes = "" [% DEFAULT shownotes = ""
episode = "" episode = ""
month_year = "" month_year = ""
@@ -52,7 +53,8 @@
</h1> </h1>
<h2>Your ideas, projects, opinions - podcasted.</h2> <h2>Your ideas, projects, opinions - podcasted.</h2>
<h3>New episodes every weekday Monday through Friday.<br /> <h3>New episodes every weekday Monday through Friday.<br />
<em><small>Temporary version of the Community News notes for hosts to use when recording</small></em></h3> <em><small>Temporary version of the Community News notes for hosts to use when recording<br />
Updated on [% date.format(date.now,'%Y-%m-%d %H:%M:%S') %]</small></em></h3>
</div> </div>
<hr /> <hr />