Community_News/make_meeting: added a new -output=FILE option
Community_News/shownote_template.tpl: removed reference to previous name
shownote_template13.tpl
Community_News/shownotes_container.tpl: rewrote to use the new website
design. Also removed some irrelevant parts like the navigation links
and the comment form.
503 lines
14 KiB
Perl
Executable File
503 lines
14 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#===============================================================================
|
|
#
|
|
# FILE: make_meeting
|
|
#
|
|
# USAGE: ./make_meeting
|
|
#
|
|
# DESCRIPTION: Makes a recurrent iCalendar meeting to be loaded into
|
|
# a calendar. This is apparently necessary when the 'RRULE'
|
|
# recurrence description is not adequate.
|
|
#
|
|
# OPTIONS: None
|
|
# REQUIREMENTS: Needs modules Getopt::Long, Data::ICal, Date::ICal,
|
|
# Date::Parse and Date::Calc
|
|
# BUGS: ---
|
|
# NOTES: Based on a script distributed with the HPR episode "iCalendar
|
|
# Hacking"
|
|
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
|
|
# LICENCE: Copyright (c) year 2012-2025 Dave Morriss
|
|
# VERSION: 0.2.4
|
|
# CREATED: 2012-10-13 15:34:01
|
|
# REVISION: 2025-10-23 16:51:48
|
|
#
|
|
#===============================================================================
|
|
|
|
use 5.010;
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Getopt::Long;
|
|
|
|
use Data::ICal;
|
|
use Data::ICal::Entry::Event;
|
|
use Data::ICal::Entry::Todo;
|
|
|
|
use Date::Parse;
|
|
use Date::Calc qw{:all};
|
|
use Date::ICal;
|
|
|
|
#use Data::Dumper;
|
|
|
|
#
|
|
# Version number (manually incremented)
|
|
#
|
|
our $VERSION = '0.2.4';
|
|
|
|
#
|
|
# Script name
|
|
#
|
|
( my $PROG = $0 ) =~ s|.*/||mx;
|
|
( my $DIR = $0 ) =~ s|/?[^/]*$||mx;
|
|
$DIR = '.' unless $DIR;
|
|
|
|
#
|
|
# Enable Unicode mode
|
|
#
|
|
binmode STDOUT, ":encoding(UTF-8)";
|
|
binmode STDERR, ":encoding(UTF-8)";
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Declarations
|
|
#-------------------------------------------------------------------------------
|
|
my ( @startdate, @rdate, @events );
|
|
|
|
#
|
|
# Attributes for the calendar message
|
|
#
|
|
my $server = 'chatter.skyehaven.net';
|
|
my $port = 64738;
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# Options and arguments
|
|
#-------------------------------------------------------------------------------
|
|
my $DEF_COUNT = 12;
|
|
#my $DEF_SUMMARY = 'Send out CNews email';
|
|
|
|
#
|
|
# Process options
|
|
#
|
|
my %options;
|
|
Options( \%options );
|
|
|
|
#
|
|
# Default help
|
|
#
|
|
usage() if ( $options{'help'} );
|
|
|
|
#
|
|
# Collect options
|
|
#
|
|
my $count = ( defined( $options{count} ) ? $options{count} : $DEF_COUNT );
|
|
my $reminder = ( defined( $options{reminder} ) ? $options{reminder} : 0 );
|
|
my $force = ( defined( $options{force} ) ? $options{force} : 0 );
|
|
my $outfile = $options{output};
|
|
|
|
#my $reminder_summary = ( defined( $options{summary} ) ? $options{summary} :
|
|
# $DEF_SUMMARY );
|
|
|
|
#
|
|
# Two reminders: 8 days ahead reminder to check with Ken, 5 days ahead
|
|
# reminder to send out the email.
|
|
#
|
|
my %reminders = (
|
|
email => [ -5, 'Send out CNews email' ],
|
|
check => [ -8, 'Check CNews date with Ken' ],
|
|
);
|
|
|
|
#
|
|
# Use the date provided or the default
|
|
#
|
|
if ( defined( $options{from} ) ) {
|
|
#
|
|
# Parse the date, convert to start of month and (optionally) validate it
|
|
#
|
|
@startdate = convert_date( $options{from}, $force );
|
|
}
|
|
else {
|
|
#
|
|
# Use the current date
|
|
#
|
|
@startdate = Today();
|
|
}
|
|
|
|
#
|
|
# Open the output file (or STDOUT)
|
|
#
|
|
my $outfh;
|
|
if ($outfile) {
|
|
open( $outfh, ">:encoding(UTF-8)", $outfile )
|
|
or die "Unable to open $outfile for writing: $!\n";
|
|
}
|
|
else {
|
|
open( $outfh, ">&", \*STDOUT )
|
|
or die "Unable to initialise for writing: $!\n";
|
|
}
|
|
|
|
#
|
|
# Date and time values
|
|
#
|
|
# TODO: These should be in a configuration file, and should ideally be capable
|
|
# of having a time zone defined (default UTC, as now).
|
|
#
|
|
my $monday = 1; # Day of week number 1-7, Monday-Sunday
|
|
my $offset = -3; # Offset from the target date (-3 is Friday)
|
|
|
|
my @starttime = ( 15, 00, 00 ); # UTC
|
|
my @endtime = ( 17, 00, 00 );
|
|
|
|
my @todostart = ( 9, 00, 00 ); # UTC
|
|
my @todoend = ( 17, 00, 00 );
|
|
|
|
#
|
|
# Format of an ISO UTC datetime
|
|
#
|
|
my $fmt = "%02d%02d%02dT%02d%02d%02dZ";
|
|
|
|
#
|
|
# Constants for the event
|
|
#
|
|
my $calname = 'HPR Community News';
|
|
my $timezone = 'UTC';
|
|
my $location = "$server port: $port";
|
|
my $summary = 'HPR Community News Recording Dates';
|
|
my $description = <<ENDDESC;
|
|
Mumble settings
|
|
-------------------
|
|
Server Name: Anything you like
|
|
Server Address: $server
|
|
Port: $port
|
|
Name: Your name or alias is fine
|
|
|
|
Information about Mumble can be found here:
|
|
http://hackerpublicradio.org/recording.php
|
|
ENDDESC
|
|
|
|
#
|
|
# Compute the next recording date from the starting date.
|
|
# Now @startdate will be today's date or the start of the explicitly selected
|
|
# month provided via -from=DATE. We want day of the week to be Monday, the
|
|
# first in the month, then to go back $offset days from that to get to the
|
|
# recording day! Simple.
|
|
#
|
|
@startdate = make_date( \@startdate, $monday, 1, $offset );
|
|
@rdate = @startdate;
|
|
|
|
#
|
|
# Create the calendar object
|
|
#
|
|
my $calendar = Data::ICal->new();
|
|
|
|
#
|
|
# Some calendar properties
|
|
#
|
|
$calendar->add_properties(
|
|
'X-WR-CALNAME' => $calname,
|
|
'X-WR-TIMEZONE' => $timezone,
|
|
);
|
|
|
|
#
|
|
# Create the event object
|
|
#
|
|
my $vevent = Data::ICal::Entry::Event->new();
|
|
|
|
#
|
|
# Add some event properties
|
|
#
|
|
$vevent->add_properties(
|
|
summary => $summary,
|
|
location => $location,
|
|
description => $description,
|
|
dtstart => sprintf( $fmt, @startdate, @starttime ),
|
|
dtend => sprintf( $fmt, @startdate, @endtime ),
|
|
);
|
|
|
|
#
|
|
# Add recurring dates. (Note that this generates RDATE entries rather than
|
|
# 1 entry with multiple dates; this is because this module doesn't seem to
|
|
# have the ability to generate the concatenated entry. The two modes of
|
|
# expressing the repeated dates seem to be equivalent.)
|
|
#
|
|
for my $i ( 1 .. $count ) {
|
|
#
|
|
# Recording date computation from the start of the month
|
|
#
|
|
@rdate = make_date( \@rdate, $monday, 1, $offset );
|
|
|
|
#
|
|
# Save the current recording date to make an array of arrayrefs
|
|
#
|
|
push( @events, [@rdate] );
|
|
|
|
#
|
|
# Add this date to the multi-date event
|
|
#
|
|
$vevent->add_property( rdate =>
|
|
[ sprintf( $fmt, @rdate, @starttime ), { value => 'DATE-TIME' } ],
|
|
);
|
|
|
|
#
|
|
# Increment the meeting date for the next one. If we're early in the month
|
|
# by one day otherwise to the beginning of the next month. This is
|
|
# necessary because otherwise make_date will skip months.
|
|
#
|
|
if ( $rdate[2] < 7 ) {
|
|
@rdate = Add_Delta_Days( @rdate, 1 );
|
|
}
|
|
else {
|
|
@rdate = ( ( Add_Delta_YM( @rdate, 0, 1 ) )[ 0 .. 1 ], 1 );
|
|
}
|
|
}
|
|
|
|
#
|
|
# Add the event into the calendar
|
|
#
|
|
$calendar->add_entry($vevent);
|
|
|
|
#
|
|
# Are we to add reminders?
|
|
#
|
|
if ($reminder) {
|
|
#
|
|
# Loop through the cache of recording dates
|
|
#
|
|
for my $i ( 0 .. $count - 1 ) {
|
|
#
|
|
# Loop through the reminders hash
|
|
#
|
|
for my $key (keys(%reminders)) {
|
|
#
|
|
# A new Todo entry each iteration
|
|
#
|
|
my $vtodo = Data::ICal::Entry::Todo->new();
|
|
|
|
#
|
|
# Get a recording date from the cache and subtract 5 days from it to
|
|
# get the preceding Monday
|
|
#
|
|
@rdate = @{ $events[$i] };
|
|
@rdate = Add_Delta_Days( @rdate, $reminders{$key}->[0] );
|
|
|
|
#
|
|
# Add the date as the date part of the Todo
|
|
#
|
|
$vtodo->add_properties(
|
|
summary => $reminders{$key}->[1],
|
|
status => 'INCOMPLETE',
|
|
dtstart => Date::ICal->new(
|
|
ical => sprintf( $fmt, @rdate, @todostart )
|
|
)->ical,
|
|
due => Date::ICal->new(
|
|
ical => sprintf( $fmt, @rdate, @todoend )
|
|
)->ical,
|
|
);
|
|
|
|
#
|
|
# Add to the calendar
|
|
#
|
|
$calendar->add_entry($vtodo);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#
|
|
# Print the result
|
|
#
|
|
print $outfh $calendar->as_string;
|
|
|
|
exit;
|
|
|
|
#=== 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
|
|
# $force Boolean defining whether to skip validating
|
|
# the date
|
|
# RETURNS: The start of the month in the textual date in Date::Calc
|
|
# format
|
|
# DESCRIPTION: Parses the date string and makes a Date::Calc date from the
|
|
# result where the day part is forced to be 1. 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
|
|
# Note the validation 'die' has a non-generic message
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub convert_date {
|
|
my ( $textdate, $force ) = @_;
|
|
|
|
my ( @today, @parsed, @startdate );
|
|
|
|
#
|
|
# Reference date
|
|
#
|
|
@today = Today();
|
|
|
|
#
|
|
# Parse and perform rudimentary validation on the $textdate date. Function
|
|
# 'strptime' returns "($ss,$mm,$hh,$day,$month,$year,$zone,$century)".
|
|
#
|
|
# 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;
|
|
|
|
@startdate = (
|
|
( defined( $parsed[5] ) ? $parsed[5] + 1900 : $today[0] ), # year
|
|
( defined( $parsed[4] ) ? $parsed[4] + 1 : $today[1] ), 1
|
|
);
|
|
|
|
#
|
|
# Unless we've overridden the check there should be a positive or zero
|
|
# difference in days between the target date and today's date to prevent
|
|
# going backwards in time.
|
|
#
|
|
unless ($force) {
|
|
unless ( Delta_Days( @today[ 0, 1 ], 1, @startdate ) ge 0 ) {
|
|
warn "Invalid date $textdate (in the past)\n";
|
|
die "Use -force to create a back-dated calendar\n";
|
|
}
|
|
}
|
|
|
|
return @startdate;
|
|
|
|
}
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: make_date
|
|
# PURPOSE: Make the event date for recurrence
|
|
# PARAMETERS: $refdate An arrayref to the reference date array
|
|
# (usually today's date)
|
|
# $dow Day of week for the event date (1-7, 1=Monday)
|
|
# $n The nth day of the week ($dow) in the given
|
|
# month required for the event date ($dow=1,
|
|
# $n=1 means first Monday)
|
|
# $offset Number of days to offset the computed date
|
|
# RETURNS: The resulting date as a list for Date::Calc
|
|
# DESCRIPTION: We want to compute a simple date with an offset, such as
|
|
# "the Sunday before the first Monday of the month". We do
|
|
# this by computing a pre-offset date (first Monday of month)
|
|
# then apply the offset (Sunday before).
|
|
# THROWS: No exceptions
|
|
# COMMENTS: TODO Needs more testing to be considered truly universal
|
|
# SEE ALSO:
|
|
#===============================================================================
|
|
sub make_date {
|
|
my ( $refdate, $dow, $n, $offset ) = @_;
|
|
|
|
#
|
|
# Compute the required date: the "$n"th day of week "$dow" in the year and
|
|
# month in @$refdate. This could be a date in the past.
|
|
#
|
|
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 the day offset
|
|
#
|
|
@date = Add_Delta_Days( @date, $offset ) if $offset;
|
|
|
|
#
|
|
# Return a list
|
|
#
|
|
return (@date);
|
|
}
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: ISO8601_Date
|
|
# PURPOSE: Format a Date::Calc date in ISO8601 format
|
|
# PARAMETERS: @date - a date in the Date::Calc format
|
|
# RETURNS: Text string containing a YYYY-MM-DD date
|
|
# DESCRIPTION: Just a convenience to allow a simple call like
|
|
# $str = ISO8601_Date(@date)
|
|
# THROWS: No exceptions
|
|
# COMMENTS: None
|
|
# SEE ALSO: N/A
|
|
#===============================================================================
|
|
sub ISO8601_Date {
|
|
my (@date) = (@_)[ 0, 1, 2 ];
|
|
|
|
if ( check_date(@date) ) {
|
|
return sprintf( "%04d-%02d-%02d", @date );
|
|
}
|
|
else {
|
|
return "*Invalid Date*";
|
|
}
|
|
}
|
|
|
|
#=== FUNCTION ================================================================
|
|
# NAME: usage
|
|
# PURPOSE: Display a usage message and exit
|
|
# PARAMETERS: None
|
|
# RETURNS: To command line level with exit value 1
|
|
# DESCRIPTION: Builds the usage message using global values
|
|
# THROWS: no exceptions
|
|
# COMMENTS: none
|
|
# SEE ALSO: n/a
|
|
#===============================================================================
|
|
sub usage {
|
|
print STDERR <<EOD;
|
|
Usage: $PROG [options]
|
|
|
|
$PROG v$VERSION
|
|
|
|
Makes a recurrent iCalendar meeting to be loaded into a calendar. Optionally
|
|
adds reminders in the form of TODO items in relation to each meeting.
|
|
|
|
-help Display this information
|
|
-from=DATE Start date for the calendar
|
|
-count=N Number of entries; default 12
|
|
-[no]force Allow a -from=DATE date before today; default not
|
|
-[no]reminder Add a reminder TODO item; default no
|
|
-output=FILE Name of file to write to; optional, writes to STDOUT
|
|
if omitted
|
|
|
|
EOD
|
|
# -summary=TEXT Alternative text for the reminder (default 'Send out
|
|
# CNews email')
|
|
exit(1);
|
|
}
|
|
|
|
#=== 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", "from=s", "count=i", "force!", "reminder!", "output=s" );
|
|
# "summary|rs=s" );
|
|
|
|
if ( !GetOptions( $optref, @options ) ) {
|
|
usage();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
|