#!/usr/bin/perl -w
our $ID = q$Id: weekly,v 1.5 2009-07-11 17:40:34 eagle Exp $;
#
# weekly -- Manage a weekly status report.
#
# Written by Russ Allbery <rra@stanford.edu>
# Copyright 1998, 1999, 2008, 2009
#     Board of Trustees, Leland Stanford Jr. University
#
# This program is free software; you may redistribute it and/or modify it
# under the same terms as Perl itself.

##############################################################################
# Initialization
##############################################################################

require 5.003;

use strict;

use Getopt::Long qw(GetOptions);
use News::FormArticle ();
use POSIX qw(strftime);

# The directory containing all of the log files and the form letter.
our $BASE = "$ENV{HOME}/data/reports";

# The abbreviated names of the months (for building a subject header).
our @MONTHS = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
               'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');

# The endings for ordinal numbers.
our @ORDINALS = ('th', 'st', 'nd', 'rd', ('th') x 17, 'st', 'nd', 'rd',
                 ('th') x 7, 'st');

##############################################################################
# Implementation
##############################################################################

# Check for command line options.  Any non-option arguments are taken to be a
# date.
my ($end, $force, $help, $nomail, $nocheck, $version);
Getopt::Long::config ('bundling', 'no_ignore_case');
GetOptions ('f|force'              => \$force,
            'h|help'               => \$help,
            'n|dry-run|just-print' => \$nomail,
            'v|version'            => \$version) or exit 1;
if ($help) {
    print "Feeding myself to perldoc, please wait....\n";
    exec ('perldoc', '-t', $0);
} elsif ($version) {
    my $version = join (' ', (split (' ', $ID))[1..3]);
    $version =~ s/,v\b//;
    $version =~ s/(\S+)$/($1)/;
    $version =~ tr%/%-%;
    print $version, "\n";
    exit;
}
if (@ARGV) {
    require Date::Parse;
    my $date = join (' ', @ARGV);
    $end = Date::Parse::str2time ($date) or die "Can't parse $date\n";
    $nocheck = 1;
} else {
    $end = time;
}

# Perform our checks.
die "Report file missing\n" unless -r "$BASE/current";
unless ($nocheck or $force) {
    die "You didn't do anything today?\n" if (-M "$BASE/current" > 1);
}
if ($force and -r "$BASE/todo" and not -r "$BASE/next") {
    rename ("$BASE/next", "$BASE/todo")
        or die "Can't rename future plans file: $!\n";
}
die "Stray todo file left around\n" if -r "$BASE/todo";
die "No future plans?\n" unless -r "$BASE/next";

# Now, build our start and end dates.  The start date is always assumed to be
# four days earlier than the end date.  We use this little sub to build dates
# rather than strftime so that we can get the right letters after the date.
sub date {
    my ($time) = @_;
    my ($month, $day) = (localtime $time)[4,3];
    return $MONTHS[$month] . ' ' . $day . $ORDINALS[$day];
}
my $start = $end - 4 * 24 * 3600;
my $range = (date $start) . ' - ' . (date $end);

# Read in our two files (what we did and what we're going to do).
my (@current, @next);
open (CURRENT, "< $BASE/current") or die "Can't open report file: $!\n";
chomp (@current = <CURRENT>);
close CURRENT;
open (NEXT, "< $BASE/next") or die "Can't open future plans file: $!\n";
chomp (@next = <NEXT>);
close NEXT;

# Generate and mail (or print to the screen) our form letter, and then archive
# our report files.
my $source = { CURRENT => \@current, NEXT => \@next, DATE => $range };
my $letter = News::FormArticle->new ("$BASE/report", $source)
    or die "Can't create form letter\n";
if ($nomail) {
    $letter->write (\*STDOUT);
} else {
    $letter->mail;
    my $archivedir = $BASE . strftime ('/%Y', localtime $end);
    my $archive = $BASE . strftime ('/%Y/%Y%m%d', localtime $end);
    unless (-d $archivedir) {
        mkdir ($archivedir, 0755) or die "Can't create $archivedir: $!\n";
    }
    rename ("$BASE/current", $archive)
        or die "Can't rename report file: $!\n";
    rename ("$BASE/next", "$BASE/todo")
        or die "Can't rename future plans file: $!\n";
}

##############################################################################
# Documentation
##############################################################################

=head1 NAME

weekly - Manage a weekly status report

=head1 SYNOPSIS

B<weekly> [B<-fhnv>] [I<date>]

=head1 REQUIREMENTS

Perl 5.003 or later and News::Article.  Date::Parse (part of the timedate
distribution) is required if a date will be given on the command line.

=head1 DESCRIPTION

B<weekly> is part of a status reporting system that periodically sends
e-mail updates about what one has accomplished.  It's intended to run as a
weekly cron job to send status reports at the end of each week.  It
performs a few checks before sending the status report to see if it looks
ready for sending and complains if it isn't, thereby acting as a reminder
to finish the report.

Inside its report directory, set at the top of the script, should be three
files: F<current>, which is the status report for the current week;
F<next>, which is the projected to-do list for upcoming weeks; and
F<report>, which is a News::FormArticle template used to generate the
e-mail message containing the weekly report.  When B<weekly> runs, it
generates a report using the F<report> template and including the contents
of F<current> and F<next>, archives F<current>, and then renames F<next>
to F<todo>.  F<todo> must therefore be renamed back to F<next>, indicating
the next report is ready for sending, before B<weekly> will send another
report.

The report template may contain the following variables:

=over 4

=item $DATE

Replaced by a string giving the dates of the weekly report.  This will be
two dates, separated by C< - >, with each date given as a three-character
English month followed by the day of the month as an ordinal.  The current
day is used as the end date of the report unless a date was specified on
the command line, in which case that date is used.  The start date is
always four days earlier than the end date (so Monday if the end date is a
Friday).

=item @CURRENT

Replaced by the contents of the F<current> file.

=item @NEXT

Replaced by the contents of the F<next> file.

=back

For B<weekly> to be happy sending a report (if run without any options),
F<current> must be modified within the past twelve hours and F<next> must
exist.

Old reports are archived after being sent.  Those archived reports are
saved in directories named after the year.  Each old report will be stored
as a file named I<YYYYMMDD> in the archive directory for that year.  The
date will be the date of the last day of the report.

B<weekly> can also take a date argument on the command line (in any form
parsed by Date::Parse), which will be used as the last date of the report
instead of the current day if present.  If a date is specified, no checks
are made for whether F<current> is recently modified.

=head1 OPTIONS

=over 4

=item B<-f>, B<--force>

If F<todo> exists in the report directory but F<next> does not, rename
F<todo> to F<next> and then continue as normal.  This flag can be used if
you don't want to have to manually rename F<todo> to F<next> to signal
that the report is ready each week, or if you're sending the report by
hand.

=item B<-h>, B<--help>

Print out this documentation (which is done simply by feeding the script
to C<perldoc -t>).

=item B<-n>, B<--dry-run>, B<--just-print>

Rather than sending the mail report, print what would be sent to standard
output.  If this option is given, the report also won't be archived and
F<next> will not be renamed to F<todo>.

=item B<-v>, B<--version>

Print out the version of B<weekly> and exit.

=back

=head1 FILES

=over 4

=item F<$HOME/data/reports>

The default report directory, set at the top of this script.  It is
expected to contain the F<current>, F<next>, and F<report> files as
described above.

=back

=head1 BUGS

The report period isn't configurable, nor is the language for encoding the
dates (which since they use ordinals isn't quite as easy as it sounds).

=head1 SEE ALSO

The current version of this script is available from Russ Allbery's script
page at L<http://www.eyrie.org/~eagle/software/scripts/>.

=head1 AUTHOR

Russ Allbery <rra@stanford.edu>

=head1 COPYRIGHT AND LICENSE

Copyright 1998, 1999, 2008, 2009 Board of Trustees, Leland Stanford Jr.
University.

This program is free software; you may redistribute it and/or modify it
under the same terms as Perl itself.

=cut