From 01d4639ba799116872f79227ee8f475470e8cef0 Mon Sep 17 00:00:00 2001 From: Dave Morriss Date: Mon, 7 Apr 2025 21:03:37 +0100 Subject: [PATCH] Updates for 'make_email' Community_News/.make_email.cfg: Removal of the SQLite database reference. No longer needed. Community_News/make_email: A lot of updates relating to confusing variable names. Rationalising the three code paths to compute the recording date (-date=DATE), the review month (-month=DATE), and the default using today's date to determine needed date information. Removed all references to the database because we don't need it any more. New date computation functions. A lot of POD updates, including an EXAMPLES section. --- Community_News/.make_email.cfg | 9 +- Community_News/make_email | 766 ++++++++++++++++++++------------- 2 files changed, 460 insertions(+), 315 deletions(-) diff --git a/Community_News/.make_email.cfg b/Community_News/.make_email.cfg index c4bbd4d..fecf23c 100644 --- a/Community_News/.make_email.cfg +++ b/Community_News/.make_email.cfg @@ -2,20 +2,17 @@ server = chatter.skyehaven.net port = 64738 room = HPR - # Default day of the week + # Default day of the week for the recording dayname = Friday # Times are UTC - starttime = 15:00:00 - endtime = 17:00:00 + starttime = 16:00:00 + endtime = 18:00:00 # Usually 2 hours are set aside. This value is used when a different start # time is provided and no end time. duration = 02 # hours # Template is in the current directory template = make_email_template.tpl - - name = hpr.db - name = recording_dates.dat diff --git a/Community_News/make_email b/Community_News/make_email index 1e4ea9e..d8c175c 100755 --- a/Community_News/make_email +++ b/Community_News/make_email @@ -27,17 +27,13 @@ # REQUIREMENTS: --- # BUGS: --- # -# NOTES: Does not send the email at present. Needs work -# 2022-02-28: DBD::MariaDB has vanished, had to revert to MySQL -# again. -# 2025-02-22: Moved to SQLite for the database -# 2025-02-23: Dropped Mail::Mailer and all related code. The -# output is now written to STDOUT or an output file. +# NOTES: Extensive rewrite. No longer needs a database and computes +# review months and recording dates itself. # # AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com -# VERSION: 0.3.3 +# VERSION: 0.3.4 # CREATED: 2013-10-28 20:35:22 -# REVISION: 2025-04-03 14:42:45 +# REVISION: 2025-04-07 18:15:30 # #=============================================================================== @@ -74,7 +70,7 @@ use Data::Dumper; # # Version number (manually incremented) # -our $VERSION = '0.3.3'; +our $VERSION = '0.3.4'; # # Script name @@ -90,7 +86,6 @@ our $VERSION = '0.3.3'; ( my $basedir = abs_path($0) ) =~ s|/?[^/]*$||mx; my $configfile = "$basedir/.${PROG}.cfg"; -my ( $dbh, $sth1, $h1, $h2, $rv ); my ( %recdates, $rdfh ); # @@ -545,7 +540,7 @@ my $cfgfile # # Sanity checking the options # -die "Unable to find $cfgfile\n" unless ( -e $cfgfile ); +die "Unable to find config file '$cfgfile'\n" unless ( -e $cfgfile ); die "Use only one of -month=MONTH or -date=DATE\n" if (defined($month) && defined($date)); @@ -563,11 +558,11 @@ _debug( $DEBUG >= 2, '%config: ' . Dumper( \%config ) ); # # Configuration file values for the email text with defaults and/or checks # -my $server = $config{email}->{server} // 'chatter.skyehaven.net'; -my $port = $config{email}->{port} // 64738; -my $room = $config{email}->{room} // 'Hacker Public Radio'; -my $duration = $config{email}->{duration} // 2; # Hours -my $dayname = $config{email}->{dayname} // 'Friday'; +my $cfg_server = $config{email}->{server} // 'chatter.skyehaven.net'; +my $cfg_port = $config{email}->{port} // 64738; +my $cfg_room = $config{email}->{room} // 'Hacker Public Radio'; +my $cfg_duration = $config{email}->{duration} // 2; # Hours +my $cfg_dayname = $config{email}->{dayname} // 'Friday'; # # If we had a start time specified then check it and ensure the end time makes @@ -586,7 +581,7 @@ if ($start) { # unless ($end) { my @end = split( ':', $start ); - $end[0] += $duration; + $end[0] += $cfg_duration; $end = join(':',@end); } @@ -601,7 +596,7 @@ else { } # -# Start and end times from options or the configuration file +# Recording start and end times from options or the configuration file # my @starttime = split( ':', $start ); my @endtime = split( ':', $end ); @@ -610,28 +605,28 @@ die "Missing start/end time(s)\n" unless ( @starttime && @endtime ); # # The template from the configuration file # -my $template = $config{email}->{template}; -die "Missing template file $template\n" unless (-e $template); +my $cfg_template = $config{email}->{template}; +die "Missing template file '$cfg_template'\n" unless (-e $cfg_template); # # Recording date cache filename in the configuration file # -my $recdatefile = $config{recdates}->{name}; -unless ($recdatefile) { +my $cfg_recdatefile = $config{recdates}->{name}; +unless ($cfg_recdatefile) { warn "No recording date file defined in configuration"; say STDERR "Continuing without this file"; } -elsif ( ! -e $recdatefile) { - warn "Can't find recording date file $recdatefile"; +elsif ( ! -e $cfg_recdatefile) { + warn "Can't find recording date file '$cfg_recdatefile'"; say STDERR "Continuing without this file"; - $recdatefile = undef; + $cfg_recdatefile = undef; } # # Load the recording dates # -if ($recdatefile) { - %recdates = load_cache($recdatefile, $rdfh); +if ($cfg_recdatefile) { + %recdates = load_cache($cfg_recdatefile, $rdfh); } _debug($DEBUG >= 2, @@ -645,112 +640,87 @@ if ($DEBUG >= 1) { } #------------------------------------------------------------------------------- -# Connect to the database -# 2021-12-24: moved to MariaDB -# 2022-02-28: the MariaDB driver has gone away apparently. Reverted to MySQL -# again -# 2025-02-22: Converted to SQLite +# Use -date=DATE, -month=DATE or default to today's date to compute or collect +# the review month and the recording date. Use Date::Calc format. +# +# If there's a -date=DATE option then it'll be an override for the recording +# date otherwise we'll compute it. #------------------------------------------------------------------------------- -my $dbname = $config{database}->{name}; - -$dbh = DBI->connect( "DBI:SQLite:dbname=$dbname", - "", "", { AutoCommit => 1 } ); - -# -# Enable client-side UTF8 -# -$dbh->{sqlite_unicode} = 1; - -# -# Date and time values using Date::Calc format -# my @today = Today(); -my @startdate; -my @startmonth; -#my @reviewdate; +my @recordingdate; +my @reviewmonth; my $monday = 1; # Day of week number 1-7, Monday-Sunday -my $offset = day_offset($dayname)->{offset}; +my $offset = day_offset($cfg_dayname)->{offset}; -#------------------------------------------------------------------------------- -# Work out the start date from the -date=DATE option, the -month=DATE option -# or the current date. -#------------------------------------------------------------------------------- -# -# If there's a -date=DATE option then it'll be an override for the start date -# otherwise we'll compute it. -# +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if ( defined($date) ) { # - # Parse and perform rudimentary validation on the argument + # Parse and perform rudimentary validation on the argument. Don't change + # the day in the date. Allow past dates it -force is on. # - my @parsed = strptime($date); - die "Invalid -date=DATE option '$date'\n" - unless ( defined( $parsed[3] ) - && defined( $parsed[4] ) - && defined( $parsed[5] ) ); + @recordingdate = convert_date( $date, 0, $force ); - $parsed[5] += 1900; - $parsed[4] += 1; - @startdate = @parsed[ 5, 4, 3 ]; - die "Date is in the past '$date'; aborting\n" - unless ( $force || Date_to_Days(@startdate) > Date_to_Days(@today) ); + # + # Determine the review month + # + if (week_of_month(@recordingdate) >= 4) { + # + # Use the month of the date + # + @reviewmonth = ((@recordingdate)[0,1],1); + } + else { + # + # Go backwards a month from the month of the recording date + # + @reviewmonth = Add_Delta_YM((@recordingdate)[0,1],1,0,-1); + } } +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ elsif ( defined($month) ) { # - # Parse the month out of the -month=DATE argument + # Parse the month out of the -month=DATE option. The day is forced to 1, + # but cannot be omitted from the option. This defines the review month. # - my @parsed = strptime($month); - die "Invalid -month=DATE option '$month'\n" - unless ( defined( $parsed[3] ) - && defined( $parsed[4] ) - && defined( $parsed[5] ) ); - - $parsed[5] += 1900; - $parsed[4] += 1; - @startmonth = @parsed[ 5, 4, 3 ]; - die "Date is in the past '$month'; aborting\n" - unless ( $force || Date_to_Days(@startmonth) > Date_to_Days(@today) ); + @reviewmonth = convert_date( $month, 1, $force ); # # Compute the next meeting date from now (by finding the next first Monday # of the month then backing up two days to the desired day - default # Friday). # - @startdate = make_date( \@startmonth, $monday, 1, $offset ); + my @nextmonth = Add_Delta_YM(@reviewmonth,0,1); + @recordingdate = make_recording_date( \@nextmonth, $monday, 1, $offset ); } +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ else { + # + # This is equivalent to the -month=DATE option except we use the current + # month which we get from @today and save it as @reviewmonth. We add + # a month to this to get the next month. + # + @reviewmonth = ((@today)[0,1],1); + my @nextmonth = Add_Delta_YM(@reviewmonth,0,1); + # # Compute the next meeting date from now (by finding the next first Monday # of the month then backing up a number of days to the required date). # - @startdate = make_date( \@today, $monday, 1, $offset ); + @recordingdate = make_recording_date( \@nextmonth, $monday, 1, $offset ); } -_debug($DEBUG >= 2, '@startdate: ' . join(',',@startdate)); +_debug($DEBUG >= 2, '$offset: ' . $offset); +_debug($DEBUG >= 2, '@recordingdate: ' . join(',',@recordingdate)); +_debug($DEBUG >= 2, '@reviewmonth: ' . join(',',@reviewmonth)); -# -# The month being reviewed is sometimes the same month and sometimes the month -# before. -# -#if ( $startdate[1] eq $today[1] ) { -# # Same month -# @reviewdate = @startdate; -#} -#else { -# # Previous month - backup 1 month -# @reviewdate = Add_Delta_YM( @startdate, 0, -1 ); -#} -# -#_debug($DEBUG >= 2, '@reviewdate: ' . join(',',@reviewdate)); - -# +#------------------------------------------------------------------------------- # Transfer Date::Calc values into DateTime objects so # we can get better formatting. -# +#------------------------------------------------------------------------------- my ( $dtrevmonth, $dtstart, $dtend ); -$dtrevmonth = dc_to_dt( \@startmonth ); -$dtstart = dc_to_dt( [ @startdate, @starttime ] ); -$dtend = dc_to_dt( [ @startdate, @endtime ] ); +$dtrevmonth = dc_to_dt( \@reviewmonth ); +$dtstart = dc_to_dt( [ @recordingdate, @starttime ] ); +$dtend = dc_to_dt( [ @recordingdate, @endtime ] ); # # Compute the number of days until the recording @@ -791,15 +761,16 @@ _debug($DEBUG >= 2, "\$monthname: $monthname", "\$nicedate: $nicedate", "\$starttime: $starttime", - "\$endtime: $endtime" + "\$endtime: $endtime", + "ISO date: ".$dtstart->ymd ); # # Build the subject line (with the recording date) # -my $waittime = ( $days > 6 ? "in $days days" : "next %A" ); -my $next = ( $days > 6 ? '' : 'next ' ); -my $forspec = ( $days > 6 ? "for $monthname " : "" ); +my $waittime = ( $days > 6 ? "in $days days" : "next %A" ); +my $next = ( $days > 6 ? '' : 'next ' ); +my $forspec = ( $days > 6 ? "for $revmonthname " : "" ); my $subject = $dtstart->strftime( "HPR Community News ${forspec}- $waittime on %FT%TZ"); @@ -830,48 +801,6 @@ for my $tz (@zones) { push( @timezones, storeTZ( $dtstart, $dtend, $tz ) ); } -#------------------------------------------------------------------------------- -# Find the number of the show with the notes. Take care because the recording -# date might not be on the weekend before the show is released. -# TODO: If this search fails (because in future Community News shows will not -# be reserved), then the date needs to be computed. -#------------------------------------------------------------------------------- -my $isodate = $dtstart->ymd; -#$sth1 = $dbh->prepare(q{ -# SELECT id FROM eps -# WHERE date > ? -# AND date_format(date,"%W") = 'Monday' -# AND title LIKE 'HPR Community News%' -# ORDER BY date -# LIMIT 1 -#}); -$sth1 = $dbh->prepare(q{ - SELECT id FROM eps - WHERE date > ? - AND strftime("%u", date) = '1' - AND title LIKE 'HPR Community News%' - ORDER BY date - LIMIT 1 -}); -$sth1->execute($isodate); -if ( $dbh->err ) { - warn $dbh->errstr; -} - -_debug( $DEBUG >= 2, "\$isodate: $isodate" ); - -unless ( $h1 = $sth1->fetchrow_hashref ) { - warn "Unable to find a reserved show on the specified date - cannot continue\n"; - exit 1; -} - -my $episode = $h1->{id}; - -_debug( $DEBUG >= 2, "\$episode (slot): $episode" ); - -$sth1->finish; -$dbh->disconnect; - #------------------------------------------------------------------------------- # Update the date cache now we have the date and time details we need. #------------------------------------------------------------------------------- @@ -887,14 +816,14 @@ if (exists($recdates{$monthkey})) { # Save the new data (assuming it's correct) # $recdates{$monthkey} = $datestamp; - update_cache( $recdatefile, \%recdates ); + update_cache( $cfg_recdatefile, \%recdates ); } } else { # # Add a new record to the end of the cache file # - append_cache( $recdatefile, sprintf( "%s,%s", $monthkey, $datestamp ) ); + append_cache( $cfg_recdatefile, sprintf( "%s,%s", $monthkey, $datestamp ) ); } #------------------------------------------------------------------------------- @@ -907,9 +836,9 @@ my $tt = Template->new( ); my $vars = { - server => $server, - port => $port, - room => $room, + server => $cfg_server, + port => $cfg_port, + room => $cfg_room, subject => $subject, timezones => \@timezones, utc => { @@ -922,11 +851,10 @@ my $vars = { start => $starttime, end => $endtime, }, - episode => $episode, # show number }; my $document; -$tt->process( $template, +$tt->process( $cfg_template, $vars, \$document, { binmode => ':utf8' } ) || die $tt->error(), "\n"; @@ -1035,7 +963,7 @@ sub update_cache { # Copy the cache file to a backup # copy($cache_name,"${cache_name}~") - or die "Unable to back up $cache_name\n"; + or die "Unable to back up '$cache_name'\n"; # # Open the original file in write mode @@ -1080,11 +1008,11 @@ sub report_settings { printf $fmt, "Start time", join(':',@starttime); printf $fmt, "End time", join(':',@endtime); printf $fmt, "Config file", coalesce($cfgfile,'undef'); - printf $fmt, "Server", coalesce($server,'undef'); - printf $fmt, "Port", coalesce($port,'undef'); - printf $fmt, "Room", coalesce($room,'undef'); - printf $fmt, "Template", coalesce($template,'undef'); - printf $fmt, "Recording date file", coalesce($recdatefile,'undef'); + printf $fmt, "Server", coalesce($cfg_server,'undef'); + printf $fmt, "Port", coalesce($cfg_port,'undef'); + printf $fmt, "Room", coalesce($cfg_room,'undef'); + printf $fmt, "Template", coalesce($cfg_template,'undef'); + printf $fmt, "Recording date file", coalesce($cfg_recdatefile,'undef'); print "D> ----\n"; } @@ -1123,6 +1051,67 @@ sub validate_time { return $time; } +#=== FUNCTION ================================================================ +# NAME: valid_month +# PURPOSE: Determines if the requested month is valid, meaning the +# current one or a future one +# PARAMETERS: $refmonth Arrayref holding the requested month +# RETURNS: True if a past month, otherwise false +# DESCRIPTION: The day of the requested month is forced to 1. Four Julian +# dates are computed: the start of the requested month, the end of the +# requested month, the current day and the start of the month +# after this one. The month is a current or future +# one if today's date is greater than the start of the month, or +# the end of the month. +# THROWS: No exceptions +# COMMENTS: None +# SEE ALSO: N/A +#=============================================================================== +sub valid_month { + my ($refmonth) = @_; + + my @month = @$refmonth; + + # + # Force the date to first of the month + # + $month[2] = 1; + + # + # Make Julian dates: + # $jstartmonth - start of the requested month + # $jendmonth - end of the requested month + # $jtoday - today + # $jstartnextmonth - the end of the current month plus 1 day + # + my $jstartmonth = Date_to_Days(@month); + my $jendmonth = Date_to_Days( + Add_Delta_Days( @month, Days_in_Month( @month[ 0, 1 ] ) ) + ); + my $jtoday = Date_to_Days( Today() ); + my $jstartnextmonth = Date_to_Days( + Add_Delta_Days( + ( ( Today() )[ 0, 1 ], 1 ), # Start of this month + Days_in_Month( ( Today() )[ 0, 1 ] ) # Days this month + ) + ); + + _debug($DEBUG >= 2, + "\$jstartmonth: $jstartmonth", + "\$jendmonth: $jendmonth", + "\$jtoday: $jtoday", + "\$jstartnextmonth $jstartnextmonth", + "----" + ); + + # + # It's valid if this month or a later one + # + return ( $jtoday >= $jstartmonth && $jtoday <= $jendmonth ) || + ($jstartmonth >= $jstartnextmonth); + +} + #=== FUNCTION ================================================================ # NAME: dc_to_dt # PURPOSE: Converts a Date::Calc datetime into a DateTime equivalent @@ -1174,27 +1163,121 @@ sub dc_to_dt { } #=== FUNCTION ================================================================ -# NAME: make_date -# PURPOSE: Make the event date for recurrence +# NAME: week_of_month +# PURPOSE: Determine the week number of the month a date is in +# PARAMETERS: $year, $month, $day fields of a Date::Calc date +# RETURNS: The computed week number within the month, in the range 1-5 +# DESCRIPTION: This is the algorithm provided in example 5 of the RECIPES +# section of the Date::Calc manpage. It uses the incoming day +# number, added to the day number of the first day of the month, +# subtracts 2 and divides by 7 then adds 1 to the result. +# THROWS: No exceptions +# COMMENTS: None +# SEE ALSO: N/A +#=============================================================================== +sub week_of_month { + my ( $year, $month, $day ) = @_; + + return int( ( $day + Day_of_Week( $year, $month, 1 ) - 2 ) / 7 ) + 1; +} + +#=== FUNCTION ================================================================ +# NAME: convert_date +# PURPOSE: Convert a textual date (ideally YYYY-MM-DD) to a Date::Calc +# date for the start of the given month. +# PARAMETERS: $textdate date in text form +# $ismonth Boolean controlling whether the date is to be +# forced to the start of the month or not. +# $force Boolean defining whether to skip validating +# the date +# RETURNS: The date or the start of the month from the textual date +# ($textdate) in Date::Calc format +# DESCRIPTION: Parses the date string and makes a Date::Calc date from the +# result making the day part is 1 if $override is true. +# Optionally checks that the date isn't in the past, though +# $force = 1 ignores this check. +# THROWS: No exceptions +# COMMENTS: Requires Date::Calc and Date::Parse +# SEE ALSO: N/A +#=============================================================================== +sub convert_date { + my ( $textdate, $ismonth, $allow_past ) = @_; + + my ( @today, @parsed, @dcdate ); + + # + # Reference date + # + @today = Today(); + + # + # Parse and perform rudimentary validation on the $textdate date. + # Using Date::Parse routine 'strptime' which returns: + # ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date) + # 0 1 2 3 4 5 6 + # + # The Date::Calc date $startdate[0] gets the returned year or the current + # year if no year was parsed, $startdate[1] gets the parsed month or the + # current month if no month was parsed, and $startdate[2] gets a day of 1. + # + @parsed = strptime($textdate); + die "Unable to parse date '$textdate'\n" unless @parsed; + + @dcdate = ( + ( defined( $parsed[5] ) ? $parsed[5] + 1900 : $today[0] ), # year + ( defined( $parsed[4] ) ? $parsed[4] + 1 : $today[1] ), # month + ( $ismonth ? 1 : $parsed[3] ) # day + ); + + # + # Unless we've overridden then perform checks: + # - if we're handling a month call validmonth + # - otherwise there should be a positive or zero difference in days + # between the target date and today's date + # this is to prevent going backwards in time. + # + unless ($allow_past) { + unless ( + ( $ismonth && ( valid_month( \@dcdate ) ) ) + || + ( !$ismonth && ( Date_to_Days(@dcdate) > Date_to_Days(@today) ) ) + ) + { + warn "convert_date: Invalid date $textdate (in the past)\n"; + die "Use -force to create a back-dated calendar\n"; + } + } + + return @dcdate; + +} + +#=== FUNCTION ================================================================ +# NAME: make_recording_date +# PURPOSE: Makes a recording date for a given review month # PARAMETERS: $refdate # An arrayref to the reference date array (usually -# today's date) +# the start of the target month) # $dow Day of week for the event date (1-7, 1=Monday) # $n The nth day of the week in the given month required # for the event date # $offset Number of days to offset the computed date -# RETURNS: The resulting date as a list for Date::Calc +# RETURNS: The resulting date as a Date::Calc list # DESCRIPTION: We want to compute a simple date with an offset, such as -# "the Saturday before the first Monday of the month". We do +# "the Friday before the first Monday of the month". We do # this by computing a pre-offset date (first Monday of month) -# then apply the offset (Saturday before). +# then apply the offset (Friday before). # THROWS: No exceptions -# COMMENTS: This function was originally written for my HPR episode on -# iCalendar. -# TODO Needs more testing to be considered truly universal -# SEE ALSO: +# COMMENTS: Derived from make_date, originally written for my HPR episode on +# iCalendar. It then got used in reserve_cnews, and was +# originally used here. It was really for generating a series of +# dates or reserving a seires of episode slots, and wasn't +# really suited for this script. +# Rather than using a date array reference it might be better to +# use a year, month, date sequence like Date::Calc does. +# SEE ALSO: N/A #=============================================================================== -sub make_date { +sub make_recording_date { my ( $refdate, $dow, $n, $offset ) = @_; # @@ -1203,18 +1286,6 @@ sub make_date { # my @date = Nth_Weekday_of_Month_Year( @$refdate[ 0, 1 ], $dow, $n ); - # - # If the computed date plus the offset is before the base date advance - # a month - # - if ( Day_of_Year(@date) + $offset < Day_of_Year(@$refdate) ) { - # - # Add a month and recompute - # - @date = Add_Delta_YM( @date, 0, 1 ); - @date = Nth_Weekday_of_Month_Year( @date[ 0, 1 ], $dow, $n ); - } - # # Apply any day offset # @@ -1274,11 +1345,11 @@ sub storeTZ { # number (wday) and the integer offset (offset) from Monday to # the recording day, or undef. # DESCRIPTION: Uses the hash '%matches' keyed by regular expressions matching -# day names. The argument '$dayname' is matched against each +# day names. The argument '$cfg_dayname' is matched against each # regex in turn and if it matches the sub-hash is returned. This -# allows the caller to use 'day_offset($dayname)->{dayname}' to +# allows the caller to use 'day_offset($cfg_dayname)->{dayname}' to # get the full name of the day if needed, or the offset as -# 'day_offset($dayname)->{offset}'. If there is no match +# 'day_offset($cfg_dayname)->{offset}'. If there is no match # 'undef' is returned. # THROWS: No exceptions # COMMENTS: None @@ -1422,25 +1493,14 @@ make_email - generates the text of an HPR Community News recording invitation =head1 VERSION -This documentation refers to make_email version 0.3.3 +This documentation refers to make_email version 0.3.4 =head1 USAGE - make_email [-help] [-documentation] [-debug=N] [-month=DATE] [-date=DATE] - [-start=START_TIME] [-end=END_TIME] [-[no]force] [-config=FILE] - - 1. ./make_email -date=2022-12-27 - Generate email for a specific date (in the future) - - 2. ./make_email -force -month=2025-03-01 -start=16:00 -out=HPR_email_%s.txt - Assume this is run in early April 2025. Normally, generating email for - March would not be allowed since it's in the past, so -force is needed - to override the checks. The start day is the default Friday, and the - start time is set to 16:00. The message (email body) is written to - HPR_email_2025-03.txt. The date cache (recording_dates.dat) will be - updated with the line: - 2025-03-01,2025-04-04 16:00:00 + make_email [-help] [-documentation|-man] + [-month=DATE] [-date=DATE] [-start=START_TIME] [-end=END_TIME] + [-[no]force] [-config=FILE] [-debug=N] =head1 OPTIONS @@ -1458,6 +1518,82 @@ Another way to see the full documentation use: perldoc ./make_email +Run this in the directory where B is kept. + +=item B<-month=DATE> + +Defines the month to be reviewed in the Community News recording for which the +email is being generated. Normally (without this option) the current month is +chosen and the date of recording computed within it. The month specified here +is provided as a ISO8601 date such as 2014-03-08 (meaning March 2014) or +1-Jan-2017 (meaning January 2017). Only the year and month parts are used but +a valid day must be present. + +=item B<-date=DATE> + +This option is a way of providing a non-default date for the recording. +Normally the script computes the next scheduled date based on the algorithm +"DAY_OF_WEEK before the first Monday of the next month" (where DAY_OF_WEEK is +the value defined in the configuration file as B) starting from the +current date or the start of the month given in the B<-month=DATE> option. If +for any reason a different date is required then this may be specified via +this option. + +The recording date should be given as an ISO8601 date (such as 2014-03-08). + +=item B<-start=START_TIME> + +The default start time is defined in the configuration file, but if it is +necessary to change it, this option can be used to do it. The B +value must be a valid B time specification. + +A change to the start time in the configuration file also implies that the end +time should change. If the B<-start=START_TIME> option is present but +B<-end=END_TIME> is not, then the end time is computed by adding a number of +hours to the start time this number is defined in the configuration file as +B. + +=item B<-end=END_TIME> + +The default end time is defined in the configuration file, but if it is +necessary to change it temporarily, this option can be used to do it. The +B value must be a valid B time specification. + +=item B<-output=FILE> + +This option defines an output file to receive the mail message text. If the option is +omitted the notes are written to STDOUT, allowing them to be redirected if +required. + +The output file name may contain one instance of the characters 'B<%s>'. This +denotes the point at which the year and the review month in the format +B are inserted. For example if the script is being run for reviewing +February 2025 the option: + + -out=HPR_email_%s.txt + +will cause the generation of the file: + + HPR_email_2025-02.txt + +=item B<-[no]force> + +Sometimes the recording of the Community News episode for a month takes place +on the first Friday of the next month. If an attempt is made to run this +script to make the email for such a recording in the month after the review +month, safety checks will prevent it. This option, which is normally off, will +allow the script to run and generate the mail message. + +Only use this if an apparent anomaly with the dates is detected. This may +happen if the recording is to be made in the month after the month being +reviewed. + +=item B<-config=FILE> + +This option defines a configuration file other than the default +B<.make_email.cfg>. The file must be formatted as described below in the +section I. + =item B<-debug=N> Enables debugging mode when N > 0 (zero is the default, no debugging output). @@ -1505,104 +1641,36 @@ The number of the show found in the database. =back -=item B<-month=DATE> - -Defines the month for which the email will be generated using a date in that -month. Normally (without this option) the current month is chosen and the date -of recording computed within it. The month specified here is provided as -a ISO8601 date such as 2014-03-08 (meaning March 2014) or 1-Jan-2017 (meaning -January 2017). Only the year and month parts are used but a valid day must be -present. - -=item B<-date=DATE> - -This is an option provides a non-default date for the recording. Normally the -script computes the next scheduled date based on the algorithm "DAY_OF_WEEK -before the first Monday of the next month" (where DAY_OF_WEEK is the value -defined in the configuration file as B) starting from the current -date or the start of the month given in the B<-month=DATE> option. If for any -reason a different date is required then this may be specified via this -option. - -The recording date should be given as an ISO8601 date (such as 2014-03-08). - -=item B<-start=START_TIME> - -The default start time is defined in the configuration file, but if it is -necessary to change it, this option can be used to do it. The B -value must be a valid B time specification. - -A change to the start time in the configuration file also implies that the end -time should change. If the B<-start=START_TIME> option is present but -B<-end=END_TIME> is not, then the end time is computed by adding a number of -hours to the start time this number is defined in the configuration file as -B. - -=item B<-end=END_TIME> - -The default end time is defined in the configuration file, but if it is -necessary to change it temporarily, this option can be used to do it. The -B value must be a valid B time specification. - -=item B<-output=FILE> - -This option defines an output file to receive the mail message text. If the option is -omitted the notes are written to STDOUT, allowing them to be redirected if -required. - -The output file name may contain the characters 'B<%s>'. This denotes the -point at which the year and the review month in the format B are -inserted. For example if the script is being run for February 2025 the option: - - -out=HPR_email_%s.txt - -will cause the generation of the file: - - HPR_email_2025-02.txt - -=item B<-[no]force> - -Sometimes the recording of the Community News episode for a month takes place -on the first Friday of the next month. If an attempt is made to run this -script to make the email for such a recording in the month after the review -month, safety checks will prevent it. This option, which is normally off, will -allow the script to run and generate the mail message. - -=item B<-config=FILE> - -This option defines a configuration file other than the default -B<.make_email.cfg>. The file must be formatted as described below in the -section I. =back =head1 DESCRIPTION -Makes an invitation email for the next Community News with times per timezone. -The message is structured by a Template Toolkit template, so its contents can -be adjusted without changing this script. +The script makes an invitation email for the next Community News recording +with times per timezone. The message is structured by a Template Toolkit +template, so its contents can be adjusted without changing this script. In normal operation the script computes the date of the next recording using -the algorithm "Saturday before the first Monday of the next month" starting +the algorithm "DAY before the first Monday of the next month" starting from the current date or the start of the month (and year) given in the B<-month=DATE> option. -It uses the recording date (B<-date=DATE> option) to access the MySQL database -to find the date on which the show will be released. It does that so the notes -on that show can be viewed by the volunteers recording the show. These notes -are expanded to be usable during the recording, with comments relating to -earlier shows being displayed in full, and any comments missed in the last -recording highlighted. Comments made to shows during the past month can be -seen as the shows are visited and discussed. +Alternatively it can use the B<-date=DATE> option to define a non-default +recording date (and time if B<-start=START_TIME> is provided). This allows the +defaults in the configuration file to be overridden. -The email generated by the script is sent to the HPR mailing list, usually on -the Monday prior to the weekend of the recording. +Only one of B<-month=DATE> and B<-date=DATE> is allowed. + +The script creates the text of the body of an email message. It is necessary +to use it with an email client to construct the message to be sent to the HPR +mailing list, usually on the Monday prior to the weekend of the recording. The +script does not send the message itself. =head1 DIAGNOSTICS =over 8 -=item B +=item B The configuration file specified in B<-config=FILE> (or the default file) could not be found. @@ -1617,29 +1685,87 @@ document. One or both of the start and end times is missing, either from the configuration file or from the command line options. -=item B +=item B The template file specified in the configuration file could not be found. -=item B +=item B -The program can generate warning messages from the database. +The script reads and writes a file, usually called B, +which is defined in the configuration file. This is used to record the date +and time of the recording for a given month for use by othger scripts. This +warning is reporting that no such file specification has been found in the +configuration file. The script will continue without it. + +=item B + +The file for holding recording dates, which is defined in the configuration +file, cannot be found. The script will continue without it. + +=item B + +The script cannot open the nominated output file for writing. + +=item B + +No output file has been nominated, so the script witll write to STDOUT. It has +failed to open this channel however. + +=item B<...: failed to open '...': ...> + +The date cache file could not be opened. + +=item B<...: failed to close '...': ...> + +Type: warning + +The date cache file could not be closed. + +=item B + +The script needs to append to the date cache and it is trying to make a backup +of it before it does so. This process has failed. =item B -An invalid date has been supplied via this option. - -=item B - -The date specified in B<-date=DATE> is in the past. - -=item B +Type: fatal An invalid date has been supplied via this option. -=item B +=item B<...: failed to truncate '...': ...> or +B<...: failed to seek in '...': ...> -The month specified in B<-month=DATE> is in the past. +Type: fatal + +The script is attempting to update a line in the date cache, but has failed to +perform an operation on it. + +=item B + +Type: fatal + +The script is checking one of the times provided and has found it to be +invalid. + +=item B + +Type: fatal + +The script has failed to validate a date and time value (built from options +such as B<-date=DATE> and B<-start=TIME>). + +=item B + +Type: fatal + +The script has failed to parse a date from B<-date=DATE> or B<-month=DATE>. + +=item B<"convert_date: Invalid date ... (in the past)> + +Type: fatal + +The date specified in B<-date=DATE> or B<-month=DATE> is in the past. There is +a supplementary message: I =item B @@ -1660,22 +1786,12 @@ needs to contain the following data: port = MUMBLE_PORT room = NAME_OF_ROOM dayname = DAY_OF_WEEK_OF_RECORDING - starttime = 18:00:00 - endtime = 20:00:00 + starttime = 16:00:00 + endtime = 18:00:00 duration = 02 # hours template = NAME_OF_TEMPLATE -=head2 DATABASE CONFIGURATION - -The program also obtains the details it requires for connecting to a SQLite -copy of the HPR database by loading them from the same configuration file in -a separate section. The data is as follows: - - - name = DBNAME - - =head2 DATE CACHE @@ -1713,6 +1829,38 @@ stored. A backup of the file is made if the data in a record is being updated. +=head1 EXAMPLES + +=over 4 + +=item 1. Specify a future recording date + + make_email -date=2025-08-01 + +Generates the text of an email for a specific date (expected to be in the +future). The first Monday of August 2025 is 2025-08-04. The date use in the +example is the Friday before that. The script will compute the month to be +reviewed (July 2025) from this date and write the email message text +accordingly, to STDOUT. + +=item 2. Specify and force a review month in the recent past + + make_email -force -month=2025-03-01 -start=16:00 -out=HPR_email_%s.txt + +Assume this is run in early April 2025 to review the episodes for March. The +recording date for that month will be computed to be 2025-04-04, with the +result being released on 2025-04-07. + +Normally, generating email for March would not be allowed since it's in the +past, so B<-force> is needed to override the checks. The start day is the +default Friday, and the start time is set to 16:00. The message (email body) +is written to B. The date cache +(B will be updated with the line: + + 2025-03-01,2025-04-04 16:00:00 + +=back + =head1 DEPENDENCIES Config::General